5.1.- Presentación y Ultimas Noticias

Saludos

Muy buenas CPV-RIANOS, aquí estamos con la nueva entrega del CPV, y van 5.Como veréis en esta lección hemos puesto el listón muy alto, esto es consecuencia del apoyo que hemos recibido de algunas personas, que se han  animado y han realizado artículos/colaboraciones. Sin ellos seguramente no estaríais disfrutando de esta lección y gracias a ellos podemos tratar temas más variados. Todo son ventajas para vosotros luego, tened en estima a estas personas e intentad imitarles ¿Vale?

Bien, fruto de esta participación, a partir de ahora vais a poder disfrutar de un nuevo visor para el CPV. Si estáis visionando esta lección con el  nuevo lector, no os digo na', simplemente consultad la ayuda y probad todas las nuevas funciones que os ofrece, si todavía utilizáis la pseudo-patata del VCPV... ¿A que esperáis para cambiar de lector?

El nuevo visor se distribuye en un archivo comprimido llamado 'VisorCPV.arj' y podéis encontrarlo, primero en Sakery Fox BBS y después en el resto de  sitios de distribución. Los subscriptores recibiran las últimas versiones del visor junto con las lecciones, el resto del personal tendrá que pasarse por una BBS o un FTP-Site para recoger las nuevas revisiones.

Las ventajas del nuevo visor no os las enumero porqué son muchas y estaría un buen rato, así que las tendréis que descubrir vosotros mismos. Espero que no hayáis tenido problemas con la instalación y conversión de artículos porqué...  sólo teníais que poner una palabra. X-DDD Si aun así, sois tan muñones que no habéis conseguido instalar el nuevo visor, leeros el fichero Instrucc.doc o mandarnos un mensaje. ;-)

Nuestra máxima en la realización del visor ha sido, intentar guardar compatibilidad con el formato de los artículos y lecciones ya publicadas para evitar tener que redistribuir todo y poder utilizar el nuevo visor con todas las lecciones, habidas y por haber. Para poder utilizar el nuevo Visor-CPV es necesario realizar una conversión de artículos, pero olvidaos de ello, ya que lo hemos montado de forma trasparente para vosotros. No os queríamos complicar la vida en la instalación como hacen otros productos, sobre todo S.O. de 32 bits ¿verdad? X-DDD

A otra cosa mariposa, como la realización esta lección a coincidido con un período de vacaciones, hemos tenido problemas con la comunicación exterior.Fido por vacaciones suele ir como el c*l*, ya que muchos sysop se van de vacantes y a pesar de su empeño, los sistemas a los 5 minutos de salir ellos por la puerta se van a la porra. Por otra parte, nuestro acceso a Intenet queda restringuido durante el verano (y tampoco quereríamos forzar la puerta de la Universidad) luego no hemos estado por ahí. Los problemas no han sido mayores ya que hemos seguido en la brecha en Sakery Fox y el CPV-05 ha sido incubado ahí, pero si que hemos tenido problemas en otros aspectos. La sección que ha pagado el pato, ha sido la del juego de la lección. Os cuento, en esta lección ibamos a incluir un juego realizado por una tercera persona, ajena a JASM-BMP, la cosa iba sobre ruedas pero cuando estabamos a punto de recibir la obra, plufff se fueron los plomos en Fido y nos quedamos con las ganas, lo sentimos mucho, cosas del teletrabajo, pero os diremos lo que suelen decir en la tele:
 

- Por causas ajenas a nuestra voluntad no podemos ofrecerles el juego de la lección que estaba previsto para estos momentos, en breve plazo, intentaremos subsanar los problemas. Muchas Gracias... ta-ri-ru-ri ....


En fin, ya os dejo con la lección, que como habreís visto en el indice tenéis mucho que estudiar. Ala, a incar los codos....
 
  


5.2.- Colaboraciones y Condiciones.

VARIOS

Lo primero de todo me gustaría hablar del bug que presentaba el programa campo de estrellas del CPV-04. En algunos casos al ejecutar la demo salen una especie de barras verticales que estropean el efecto, pues bien, al parecer se deben al orden con que ejecutéis las demos, si probáis el campo de estrellas lo primero de todo al arrancar el ordenador, todo ira bien, pero si se ejecuta detrás de algún otro programa la cosa se chafa. He intentado hablar con el autor pero está incomunicado, luego seguiremos con la duda de la causa. Igual está hecho a proposito y todo, X-DDD ¿Quién sabe...? Por cierto, al probar la demo con el nuevo visor, el problema aparece muchas menos veces... SI ES QUE ES BUENO HASTA PA' ESO, LESHE....

Bien, una vez aclarado esto, ahora me tocaría intentar 'convenceros' de que si no os habéis registrado/subscrito todavía al CPV, lo hagáis sin perder tiempo, pero creo que a estas alturas, sobrán las explicaciones, simplemente los megas y megas de código y artículos son nuestra mejor razón. Mirad lo que ha sido el CPV hasta ahora, pensad sobre si queréis que continue y decidid si nos merecemos una chocolatina. ;-)

Para disfrutar por completo del CPV, de todos los fuentes y del resto de ventajas extras, debes solicitar las lecciones por correo. Para ello es necesario aportar la irrisoria cantidad de 500 pesetas por lección, diskette HD, sobre de seguridad y gastos de envio incluidos. Las formas de pago son las siquientes:

*****************************************************************************
*  Recordad, pedidos a partir de la tercera lección inclusive, los números  *
*  1 y 2 se envian con la lección 3.                                        *
*****************************************************************************

- Giro Postal a nombre de cualquiera de los autores, indicando en los datos del remitente la dirección para el envío de la lección y los números solicitados. Nuestras direcciones son las siguientes:

        Jesús Angel Sánchez Mena          Benjamín Moreno Palacios

        [Datos adicionales omitidos en la versión HTML, porque no
         tengo información de que este proyecto tenga continuidad
         actualmente, ni soy quien para distribuir datos personales
         -que posiblemente ya no sean correctos- de los autores}

- Mandar una carta de petición a cualquiera de los autores, dónde se indique claramente la dirección para el envío y la fecha y código de identificación de la trasferencia bancaria a alguna de las siguientes cuentas:

        [Datos adicionales omitidos en la versión HTML, porque no
         tengo información de que este proyecto tenga continuidad
         actualmente, ni soy quien para distribuir datos personales
         -que posiblemente ya no sean correctos- de los autores}

Si os gusta el riesgo y la aventura podéis optar por mandar dinero en metálico en la carta de petición, pero no nos hacemos responsables de las posibles 'negligencias postales'.

Ya sabéis en cualquiera de las modalidades anteriores el importe mínimo (se aceptan donativos y propinas) válido es de 500 ptas, transporte, I.V.A., T.A.E., impuesto de matriculación, de lujo, de sociedades, Plan Renove I y II e impuestos de los impuestos incluido. ;-)

Para cualquier duda que tengáis, poneros en contacto con nosotros, nuestras  direcciones electrónicas las obtendréis con la tecla de función F4. En el directorio principal de esta lección, os encontraréis con una plantilla 'pedido.doc' para hacer vuestro pedido por correo.

Bueno, expondremos ahora las condiciones del CPV, sabemos que son un rollete pero los autores debemos tener algo dónde agarrarnos:

** NECESARIO **   *** NECESARIO ***    *** NECESARIO ****   ** NECESARIO **
 


** NECESARIO **   *** NECESARIO ***    *** NECESARIO ****   ** NECESARIO **
 
  


5.3.- Modo 4x13h II.

4x13h 2/3

Bueno amigos, vamos a empezar con la segunda parte de la explicación de nuestro particular modo gráfico 4x13h (un pseudo modo X). Así que abrocharos los cinturones, que empezamos con la movida.

En el anterior capítulo, nos quedamos escribiendo acerca de cómo se realizaba la visualización de un único punto en la pantalla en nuestro modo X (a partir de ahora me referiré así a 4x13h, por facilidad de escritura). Es importante que recordéis la cantidad de cosas que había que hacer para llegar hasta la visualización de dicho punto en la pantalla, en resumen había que: cambiar al modo X (lo cual incluía el cambio a modo 13h normal, y la re-programación de multitud de registros de la VGA), luego debíamos calcular en que bit-plane estaba situado ese punto (si no recordáis el concepto de 'bit-plane' ir al artículo anterior, ya que durante esta explicación lo estaremos utilizando constantemente), programar otra serie de registros de la VGA en función de este bit-plane calculado, y por fin llevar el color del punto a la zona de la pantalla de vídeo.

Como podéis ver, el proceso para sacar un solo punto por pantalla es muy considerable. Lo bueno es que sabiendo esto, ya seríamos capaces de sacar un sprite en una posición de la pantalla, ya que un sprite no es mas que una colección de puntos a colocar en una posición determinada. Pero si tardamos en visualizar un punto ,imaginaros cuando tengamos que sacar 2000 o 3000 puntos que puede contener un sprite de los 'pequeños' o aun peor los 64000 de una pantalla completa. :-( Como bien deduciréis, el tiempo que conlleva este proceso es MUY grande, así que nuestra próxima tarea será minimizar este tiempo que nos fastidia tanto.

Y ¿cómo lograremos esto?, pues muy sencillo. El hecho de que este tiempo sea tan grande, es debido a que para cada punto tenemos que realizar las tareas de calcular en que bit-plane está, calcular su posición en pantalla,  programar los registros de acuerdo con el bit-plane calculado y poner el punto. Es fácil de ver que los puntos consecutivos de un sprite estarán siempre en bit-planes distintos (si un punto está en el bit-plane 1, el siguiente estará en el bit-plane 2 y así sucesivamente) con lo cual siempre tendremos que reprogramar los registros correspondientes. Pero, ¿y si agrupamos los puntos correspondientes a cada bit-plane todos juntitos?. Veamos en que se convierten cada uno de los problemas que teníamos anteriormente...

-> Calcular en que bit-plane está. Esto solo lo tendremos que hacer en el primer punto del sprite, ya que todos los agrupados con él, pertenecerán al mismo bit-plane. Para el resto, bastará con incrementar el número de bit-plane.

-> Programar los registros de acuerdo con el número de bit-plane. Una vez iniciada la salida de los puntos de un bit-plane, no tendremos la necesidad de re-programar dichos registros, ya que ya tendrán el valor del bit-plane correcto. Esto se traduce en que en vez de tener que realizar 2000 o 3000 programaciones, sólo tendremos que realizar 4 (una para cada bit-plane). Esta es la gran ventaja de este método que os estamos mostrando.

-> Poner el punto. Las direcciones de memoria para puntos consecutivos, serán direcciones consecutivas, así que no tendremos más que incrementar la variable que nos sirva como puntero a la memoria de vídeo.

Esto conlleva que tendremos que rehacer la forma de almacenamiento de nuestros sprites en memoria para adaptarlos a este nuevo método. Como sé que lo anterior no os va quedar demasiado claro, os lo voy a explicar con un pequeño ejemplo. Suponed que los siguientes datos corresponden a un sprite con una única fila. Observad lo que supone reconvertir el sprite al nuevo formato.

             A1                   A2                  A3
      __________________   _________________   _________________
     /                  \ /                 \ /                 \
     +----+----+----+----+----+----+----+----+----+----+----+----+
     | D1 | D2 | D3 | D4 | D5 | D6 | D7 | D8 | D9 |D10 |D11 |D12 |
     +----+----+----+----+----+----+----+----+----+----+----+----+
       b1   b2   b3   b4   b1   b2   b3   b4   b1  b2   b3   b4
 

        Dxx = Datos.
        Ax  = Dirección de memoria de vídeo donde hay que situar dicho punto.
        bx  = Bit-plane donde se sitúa el punto.

Fijaros lo que os decía. Cada uno de los puntos se sitúa en bit-planes distintos (consecutivos). Esto es lo que nos hace tener que reprogramar en cada paso los registros de la VGA, pero si reagrupamos el sprite de la siguiente forma:

       A1   A2   A3   A1   A2   A3   A1   A2   A3   A1   A2   A4
     +----+----+----+----+----+----+----+----+----+----+----+----+
     | D1 | D5 | D9 | D2 | D6 |D10 | D3 | D7 |D11 | D4 | D8 |D12 |
     +----+----+----+----+----+----+----+----+----+----+----+----+
       b1   b1   b1   b2   b2   b2   b3   b3   b3   b4   b4   b4

Hemos agrupado los datos de cada uno de los bit-planes juntos, con lo cual, sólo deberemos re-programar los registros al comenzar (para el primer punto), al pasar de D9 a D2, de D10 a D3 y de D11 a D4. Es decir 4 veces en lugar de 12. Imaginaros ahora un sprite con 2000 puntos en vez de este pequeñín. Reduciríamos el número de reprogramaciones de 2000 a 4, cojonudo, ¿no?

Como no, este método tiene sus cosas buenas, y sus cosas malas. Como siempre la cosa buena es el aumento de velocidad, y como casi siempre la cosa mala es el aumento en la complicación. Para adecuarnos al nuevo método tendremos que tener en cuenta una serie de cosas:

-> Como podemos observar las direcciones de memoria comienzan en A1 y se incrementan con cada dato dentro de un mismo bit-plane. Pero al cambiar de bit-plane, debemos comenzar otra vez en la dirección inicial A1.Esto implica que deberemos guardar en algún sitio esta dirección de comienzo, para poder restaurarla posteriormente.

-> Oto pobema, si bien el primer formato se adecuaba perfectamente a lo que es la estructura de un fichero crudo '.CEL', el segundo no se adecua en absoluto. Para cargar el sprite entonces podemos optar por una de las siguientes opciones:

. Cargar el fichero '.CEL' y tratarlo de una forma especial para terminar almacenándolo en formato adecuado. Esto implica realizar el la conversión durante la ejecución del programa, lo cual lo ralentiza bastante.

. Realizar una especie de 'pre-compilación' del fichero '.CEL' y trasformarlo a nuestro formato, ANTES DE QUE NUESTRO PROGRAMA LO CARGUE, dejando el fichero resultado de la siguiente forma:

 +---------+--------+-------------+-------------+-------------+-------------+
 | Anchura | Altura | bit-plane 1 | bit-plane 2 | bit-plane 3 | bit-plane 4 |
 +---------+--------+-------------+-------------+-------------+-------------+
   1 byte    1 byte    Anchura/4     Anchura/4     Anchura/4     Anchura/4
                         bytes         bytes         bytes         bytes

De esta forma, nuestro programa no tendrá más que cargar directamente el fichero en memoria, ya que éste se encontrará en el formato correcto. Nosotros por nuestra parte ya hemos realizado un programa que realiza esta conversión (con la corrección de que en el campo 'Anchura' guarda directamente el valor 'Anchura/4'). Dicho programa se denomina 'CEL2SPR.C' y debería ir incluido con los fuentes de esta lección, dependiendo de si habéis sido buenos o no. ;-)

-> El pobema ma' godo. Si nuestro sprite tiene una anchura que no sea múltiplo de 4, tendremos dificultades, ya que a cada fila tendremos que, o bien recordar en que bit-plane comenzamos, o bien recalcularlo, el caso es que se nos complica demasiado las cosas. Por ello, nosotros hemos tomado como norma, coger sprites con anchura múltiplo de 4 EXCLUSIVAMENTE. Si intentáis utilizar nuestras funciones con sprites que no cumplan esta norma, seguramente NO FUNCIONARAN.

Bueno, pues con la información disponible en este artículo y el de la lección anterior, ya deberíais ser capaces de realizaros una rutina super-óptima para poner sprites en la pantalla en modo-X. Habréis observado que hasta ahora con este modo todo han sido pegas y dificultades:

- Pos vaya cochas que nos encheñan JASM y BMP. - Diréis

Bien, si habéis entendido y asumido todo lo que os hemos contado hasta aquí, veréis que a partir de ahora comienza la parte realmente fascinante del modo X y comprenderéis porqué muchos programadores de video-juegos se decantan por este modo de vídeo ...

. Concepto de Múltiple Página
 

Aunque ya os lo comentamos en el anterior artículo, os lo volveremos a recordar ahora. Con la programación de los registros de la VGA que realizamos cuando empezamos el modo X, lo que conseguimos es obtener varias páginas de vídeo en vez de una. Como también hemos visto, en este modo cada dirección de memoria contiene la información de 4 puntos distintos (uno por cada bit-plane) y es por ello que para acceder a dichos puntos tenemos que programar adecuadamente más registros de la VGA.

Es decir que en 16000 bytes de memoria +--------------------------+
de vídeo tendremos 64000 datos        | 16Kb A000:0000-A000:3FFF |
(una pantalla completa), pero resulta  |        Página-1          |
que tenemos 64 Kb de memoria de vídeo. +--------------------------+
Es decir que, como muestra la figura,  | 16Kb A000:4000-A000:7FFF |
tendremos una extensión correspondiente|        Página-2          |
a 4 páginas de vídeo.                 +--------------------------+
                                      | 16Kb A000:8000-A000:BFFF |
     Además en este nuevo modo, tenemos|        Página-3          |
la posibilidad de visualizar por la    +--------------------------+
pantalla aquella página que queramos.  | 16Kb A000:C000-A000:FFFF |
Así pues, podemos estar visualizando   |        Página-4          |
la página 3, y estar mandando datos a  +--------------------------+
la página 1, sin parpadeos ni nada,
dibujando a nuestras anchas =8-))

Para realizar este cambio que os he mencionado, hay que 'tocar' un par de registros de la VGA. Estos dos registros se encuentran dentro del chip del controlador de CRT, y son los siguientes:

        Controlador CRT -> 3D4h
        Registro de datos del CRT -> 3D5h
        Registro 'Start Address LOW' -> 0Ch
        Registro 'Start Address HIGH' -> 0Dh

Estos registros funcionan de la siguiente manera. Supongamos que queremos visualizar una página que se encuentra a partir de la dirección A000:8000. El siguiente programilla en ensamblador realizaría dicha tarea...

        mov     dx,3d4h             ; Metemos en DX el valor del CRT
        mov     al,0ch              ; Programamos el registro 'S. A. LOW'
        out     dx,al               ; Se lo enviamos a la VGA
        inc     dx                  ; Pasamos al registro de datos del CRT
        mov     al,00               ; Le enviamos la parte baja de 8000h
        out     dx,al               ; Se lo enviamos a la VGA
        dec     dx                  ; Volvemos a meter en DX el valor del CRT
        mov     al,0dh              ; Lo mismo para la parte alta...
        out     dx,al
        inc     dx
        mov     al,80h              ; Parte alta del 8000h
        out     dx,al

este era el código sin optimizar en absoluto para que lo vierais, pero en particular esta función conviene optimizarla al máximo, con lo cual el código podría quedar así:

        mov     dx,3d4h
        mov     ax,000ch            ; Parte Baja al registro 'S. A. Low'
        out     dx,ax
        mov     ax,800dh            ; Parte Alta al registro 'S. A. High'
        out     dx,ax

Animaciones de sprites en modo X

Bueno, atención ahora que viene el plato fuerte del artículo. El procedimiento general que, en el modo 13h normal, se sigue para efectuar las animaciones de los sprites es que, primero todos los sprites se vuelcan a una página virtual y luego ésta, se vuelca con todos los sprites 'pintados' a la memoria de vídeo. Aquí la forma de realizar la animación cambia radicalmente, debido a que disponemos de varias páginas de vídeo, debemos aprovecharlas, y el proceso que se realiza para aprovechar al máximo esto es el siguiente:

-> Paso 1: Nuestra animación constará de un fondo que permanecerá constante y unos sprites que serán los que se animarán.

-> Paso 2: Dibujaremos en una de las páginas de vídeo (la 1 por poner alguna) los sprites. Si tenemos almacenado en alguna página de vídeo (la 4) el fondo original, sobre el que se sitúan dichos sprites, luego podremos restaurarlo. Visualizamos la página 1.

-> Paso 3: Mientras se está visualizando la página anterior, en  otra (la 2) dibujamos los sprites una posición más avanzada en nuestro proceso de animación. Cuando los sprites estén dibujados, cambiamos y visualizamos esta página (2).

-> Paso 4: Ahora, lo primero, restauramos el fondo de los sprites dibujados en la página que ha dejado de ser visualizada (la 1) y después, volvemos a dibujar los sprites una posición más avanzada que en la página actual (la 2). Volvemos a realizar un cambio y visualizamos la página tratada.

Repitiendo los pasos 3 y 4 se realizará la animación. En resumen, los pasos son dibujar, cambiar, restaurar, dibujar, cambiar, restaurar, dibujar cambiar....

Como podéis observar, este método realiza las animaciones mediante el uso de dos páginas. Mientras una se está visualizando por pantalla (página mostrada), en la otra se realizan los cambios (página activa). Cuando todas las modificaciones han sido realizadas, se hace un cambio, y se visualiza la página anteriormente activa.

Teniendo en cuenta que el cambio de página visualizada es casi instantáneo, las animaciones que obtenemos mediante este método son 'acojonantemente nítidas', ya que nunca se vuelca nada a la pantalla que se está visualizando, siempre se hace a la página oculta.

Un último detalle, como veis durante el proceso de animación sólo se utilizan tres páginas, dos para el intercambio y otra para almacenar el fondo permanentemente, luego tenemos una página libre (en nuestro caso la 3) para hacer lo que nos venga en gana, por ejemplo almacenar los sprites.

Bueno, a ver que os parece la demo que os hemos preparado ...

Pulsa F2 para ejecutar el programa Demo_X.   - Posibilidad no disponible en la versión HTML-

Creo que ya basta de rollazo por este artículo. La verdad es que si habéis entendido y asumido todo lo que os hemos explicado en él, lleváis muchísimo adelantado, ya que esta filosofía de tener dos zonas de memoria, una activa que se usa en un momento determinado, y otra pasiva donde se realizan los cambios, es muy usada no solo en el modo X si no en muchas otras áreas, por ejemplo, en la salida de sonidos mezclados por la SB.

A esta técnica, por culturilla general, se la denomina 'double buffering' o 'Técnica del doble buffer'. Creo que con lo que llevamos habréis aprendido por lo menos un 60 o 70 por ciento de lo que se debe saber sobre el modo X. En el próximo artículo del modo X veremos como se realizan los scrolles en esta organización de vídeo, y será aquí cuando comprendáis realmente el porqué se utiliza el modo X en los juegos comerciales, hasta la próxima...
  


5.4.- Tablas de Pregeneración.

Look-Up.

- Por José Miguel Espadero -
- Demo por Francisco Priego -

Las tablas de pregeneración (Look-Up al otro lado del charco) son una técnica muy empleada en cualquier aplicación que necesite hacer cálculos matemáticos complejos frecuentemente. Su objetivo es evitar repetir continuamente estas operaciones, que suelen consumir mucho tiempo y por tanto ralentizan a nuestros programas. No importa si el ordenador donde se compilará el programa o el ordenador donde se ejecutarará el mismo, disponga o no de coprocesador matemático: las tablas de pregeneración acelerarán los cálculos en la gran mayoría de los casos.

El ejemplo más común es la pregeneración de tablas trigonométricas (es decir: senos, tangentes, arcosenos, etc...), que son totalmente imprescindibles en el mundillo de las 3D, raytracing/raycasting y en el área de las demos. La demo de este artículo y la del campo de estrellas del CPV-03, se apoyan en el uso de esta técnica. El uso de las tablas de pregeneración, no sólo se reduce al campo de la trigonometría, sino que sus características la hacen muy recomendable para toda clase de aplicaciones de cálculo de complejos, de cálculo numérico, de generación de fractales , etc...

El funcionamiento de una tabla look-up es bastante simple. El objetivo es calcular al principio de nuestra aplicación todas aquellas operaciones matemáticas que podamos necesitar y almacenar los resultados en una tabla indexada para su posterior recuperación. De esta manera, al comienzo de nuestro programa tendremos que 'detenernos' a realizar muchas cuentas, que en bastantes casos no necesitaremos, pero... después, durante la ejecución no tendremos que realizar esas operaciones lentas sino que buscaremos directamente la solución (o una buena aproximación) en la tabla que nos pregeneramos. En principio y dependiendo de los métodos de acceso a la tabla y del número de elementos de la misma, el acceso a una posición de una tabla es mucha más rápida que cualquier operación en coma flotante, trigonométrica, logarítmica y demás, luego: ¡He ahí la ventaja!

Podemos hablar de dos tipos de tablas Look-Up según la forma de crearlas:

-Las tablas de pregeneración completa se utilizan cuando vamos a emplear una función en un número finito de puntos conocidos con antelación o cuando usamos una función que toma infinitos valores pero se pueden reducir a un número finito de ellos. El ejemplo más claro lo tenemos en la función seno, esta función toma infinitos valores pero puede reducirse al intervalo [-3.14.., +3.14..] mediante la función módulo. En este intervalo también se podrán tomar infinitos valores al ser una función de variable real, por ello, tendremos también que discretizar el mismo para poder definirmos una  tabla finita.

 Los índices de la tabla serán números enteros que representen los valores del intervalo que puede tomar la función. El contenido de cada posición de la tabla será el resultado de la función aplicada a su valor índice. Cuantos más valores contenga una tabla Look-Up, más precisa será la función pregenerada, aunque ocupará más memoria (que nos acabará faltando, ¡seguro!) y, si no tenemos cuidado con la forma de acceso, puede incluso ser más lenta.

-Las tablas de pregeneración cache son bastante más complicadas de implementar y de acceder. Se emplean cuando la función no puede discretizarse a un número racional de valores (por ejemplo, proyectar todos los puntos 3D vistos desde determinado punto de vista) o cuando el número de puntos donde vayamos a aplicar la función no supera cierta relación (dependiente de cada función) que hace que el tiempo total de pregeneración y consulta sea menor que el tiempo con el método de cálculo directo.

 Un ejemplo: sabemos que cierto modelo 3D tiene un número finito de puntos (nuestras esferas tienen 648 caras cuadradas, luego hay 648 vértices que se proyectan 4 veces cada uno), cuando vamos a proyectar un punto miramos si está en la tabla: Si el punto ya está en la tabla se lee y se emplea; si el punto no está en la tabla se proyecta y se introduce en la tabla.

Las tablas de pregeneración cache son muuuucho más delicadas, pues el tiempo para decidir que un elemento no está en la tabla + el tiempo de inserción en la tabla + tiempo de acceso posterior al elemento, debe ser en media menor queel tiempo de cálculo del valor todas las veces que se emplee, por lo quedebemos usar tablas de tipo ordenado o arborescentes (muuuuy complicadas) eimplementarlas lo mejor que podamos en ensamblador.

Yo he implementado una para mis aplicaciones 3D y la verdad es que deja bastante que desear. Trataré de mejorarla durante el verano y veré si se puede publicar en las últimas lecciones del CPV. Por ahora sólo nos centraremos en las de pregeneración completa o parcial.

La forma de generar una tabla Look-Up es la siguiente:

-Primero decidiremos cuantos elementos vamos a pregenerar: Si se trata de  funciones discretas, como el factorial, usaremos tantos elementos como el máximo que vayamos a usar. También es posible guardar en la tabla sólo los  que se vayan a emplear, por ejemplo, entre el 1000 y el 1500 -> 500 elementos.

 Cuando tratemos con funciones reales decidiremos a cuantos valores vamos a reducirla de pendiendo del uso que le vayamos a dar:

 El ejemplo más sencillo es la función seno que se puede expresar en grados en lugar de radianes. Para aplicaciones numéricas la división en 360 valores es ridícula, un valor aceptable sería 36000. Para aplicaciones gráficas nos conformaremos con 720 valores, que proporcionan una precisión de medio grado.

Entonces hacemos:

             TYPE tabla = array [ Cto_Origen ] of TipoResultado;

En nuestro ejemplo:

             TYPE tabla = array [0..719] of Real;

Y sobre todo lo que NO hay que hacer nunca es algo como:

             VAR miTabla : tabla;

pues nos ocuparía memoria dentro de las 64K permitidas para variables estáticas (Si se trata de una demo cortita se puede perdonar). Lo que haremos en su lugar es:

              VAR miTabla : ^tabla;
                      :
                      :
              New (miTabla);

Sin olvidarnos nunca liberar el espacio antes de salir del programa utilizando la función:

              Dispose(miTabla);

-Ahora pregeneramos los valores de la función en los puntos de la tabla y los almacenamos en la misma. Dentro de los cálculos es posible emplear los valores que ya hayamos calculado (como para el factorial). En nuestro ejemplo:

     PI360 := PI / 360;        (* esto es una tabla de pregeneración de *)
                               (* un solo elemento, pero ayuda mucho *)
     for i := 0 to 719 do
        miTabla^[i] := Cos(i * PI360); (* genera los 720 valores *)

-Para utilizar la tabla cambiamos toda referencia a la función por llamadas a nuestra tabla. Es importante escribir los programas de forma que usemos los menores factores de conversión posibles, es decir, escribir los bucles pensando que nuestra circunferencia tiene 720 medios-grados en lugar de escribirlo para trabajar con reales, y luego pasarlos a nuestra escala. Esto no siempre es posible, pero acelera mucho más el resultado final;

 Donde ponía:

          x := 74 * Cos(ang*PI360);  (* ang de tipo integer *)

 Podemos poner:

          x := 74 * miTabla^[ang];

                      ¿ Cuando generar las tablas Look-Up ?

El gran inconveniente de tener que pregenerar todos los valores de una función
es que el proceso puede tomarse cierta cantidad de tiempo dependiendo del
tamaño de la misma y lo complicada que sea.

Hay varias formas de generar las tabla Look-Up:

1. La fea: Empleada en los programas antiguos. Se pone en la pantalla del ordenador "ESPERA UN MOMENTO" con letras parpadeantes y se crean las tablas. La gente aprovechará para escribir el nombre del juego y rápidamente ir a hacer pis. Si no puedes evitarlo te aconsejo que escribas:

                           ESPERA UN MOMENTO
                       GENERANDO LAS TABLAS LOOK-UP

con lo que quedara "mucho más profesional".

2. La antipática : Es parecida a la anterior pero no se escribe nada en la pantalla. Así el que tenga un ordenador un poco antiguo se desespera, pensará que el juego no corre en su ordenador y pulsará todas la combinaciones posible de teclas entre el Ctrl, Alt y el Esc.

3. La disimulada : Se genera la tabla con un programa aparte y se almacena en un fichero. Cuando se carga el juego lo primero que se hace es cargar la tabla directamente en memoria. Lo malo es que tus juegos ocupan más disco y a veces el tiempo resultado total es mayor que el de generar la tabla a lo bruto. La mayoría de la gente no os protestará por esto porque piensan que estás cargando unos gráficos maravillosos que se usarán en el último nivel. Por cierto DOOM II usa este método. Recomiendo usarlo solo para funciones realmente recalcitrantes.

La forma de cargar una tabla desde el disco ha de ser siempre con funciones de tipo BlockRead, NO se os ocurra hacer algo como esto:

                Reset(fichTabla);
                for cont := 0 to 719 do
                    read(fichTabla, miTabla[cont]);

porqué pueden aparecer los tomates voladores sobre vuestras cabezas (:.

4. La aparente : Se copia una impresionante pantalla de presentación en la pantalla y en lugar de hacer una pausa de 2 segundos, se hace una pausa de medio segundo y se pregenera la tabla. Un buen programador no se conforma con menos (los hay que aprovechan para pedir el pago del Share por adelantado :) ), pero para qué usarla si tenemos...

5. La Bomba : ¿ Necesitas varias tablas Look-Up ? ¿ Necesitas una tabla gigantesca ?. No importa, este método te permite generarlas sin gastar nada de tiempo y subiendo tu autoestima por los aires.

- Primero cargas una imagen y la vuelcas por pantalla con la paleta negra (CPV2).

- Ahora se hace un efecto de fundido ascendente (CPV3), e introduces la instrucción para generar uno o más elementos de la tabla justo antes del retrazado vertical. O mejor todavía, usa un retrazado de la forma:

               repeat
                  <<generar elemento i-esimo>>; inc(i)
               until port[$3Da] and 8 = 0;
               repeat
                  <<generar elemento i-esimo>>; inc(i)
               until port[$3da] and 8 <>0;

- Cuando se haya terminado el fundido debes hacer una pausa para que se pueda ver tu obra de arte. Aquí vale todo mientras seas capaz de mantener constante el tiempo de pausa entre ordenadores distintos. Como los retrazados verticales son independientes del ordenador y teniendo en cuenta que duran una centésima de segundo aproximadamente, un método puede ser esperar xxx retrazados haciendo:

                time := 0;
                repeat
                    repeat
                      if i <= maxElementos then begin
                          <<generar elemento i-esimo>>; inc(i) end
                    until port[$3Da] and 8 = 0;
                    repeat
                      if i <= maxElementos then begin
                          <<generar elemento i-esimo>>; inc(i) end
                    until port[$3da] and 8 <>0;
                    inc(time)
                until time > xxx;

Que nos mantiene un tiempo estable en todos los ordenadores y nunca genera más elementos de los que necesitemos. A la salida del bucle podemos saber si hemos terminado de generar la tabla comparando el valor de i con el de maxElementos. En total habremos estado cerca de 3 segundos (mucho tiempo) haciendo cálculos, lo que os da tiempo a hacer verdaderas maravillas.

 En la siguiente demo hemos intentado utilizar de todas las posibilidades la más sencillas, utilizamos tabla de pregeneración completas con generación  "antipática", pero lo hemos hecho por una causa justa, ;-) para que  entendáis la tablas Look-up y daros algo que mejorar en esta demo.

Pulsa F2 para ejecutar el programa Look-Up.    - Posibilidad no disponible en la versión HTML-
 

                      Hasta la próxima colaboración.
 
 
 



5.5.- Programación de Residentes.

TSR.


- Por Francisco Priego (Paco ¡¡P!!) -

Bien... y... como me dijo Mr. JASM: "los TSRs no tienen mucho que ver con la programación de vídeo-juegos ¿no...?". Pues, no... y sí.

En lecciones anteriores del CPV hemos visto el funcionamiento de las interrupciones, sus rutinas de atención asociadas (ISR: Interrupt Service Routines), y el potente uso que les podemos dar (recuerda los códigos SCAN, y la música en background por ejemplo) en nuestros programas.

Pues un TSR tiene casi la misma filosofía de uso que los programas que hemos visto que desvían las interrupciones, con algunos pequeños añadidos, como la propia residencia en memoria de el código de nuestras ISR.

Antes de empezar, hay que aclarar que la programación a bajo nivel, como en este caso, es mejor hacerla en ASM, pero veremos como desde un LAN podemos hacer cosas, cuando menos interesantes ;).

La filosofía de un TSR
**********************

¡Es fácil hacer un residente!, es más, está tirao (pero no te pongas muy, muy contento, que ya verás que el problema no está en la residencia del programa), el propio DOS nos ofrece las siguientes interrupciones para dejar residente en memoria un programa o unos datos:

  INT 21,31 - Terminate Process and Remain Resident

    AH = 31h
    AL = exit code (returned to batch files)
    DX = memory size in paragraphs to reserve

  INT 27 - Terminate and Stay Resident

    DX = offset of last byte in program to remain resident plus 1
    CS = segment of PSP

Nosotros, desde un lenguaje de alto nivel (C en este caso) tenemos directamente una función implementada por borland para dejar residente un programa:

       void keep(unsigned char estado,unsigned tamaño_mem_en_párrafos)

que en realidad es una llamada al servicio 31h de la int 21h del DOS, siendo un párrafo = 16 bytes.

Bien, estamos MUY contentos, hemos dejado nuestro código residente en memoria, estamos en el prompt del DOS y... ¡leshes!, ¿como hago para entrar en el programa que he dejado residente?

La idea es "enganchar" nuestra rutina a alguna interrupción

(generalmente la int del teclado, reloj, actividad del disco, etc), de forma que cuando se produzca esa interrupción, aparezca nuestro programa si se cumplen unas condiciones determinadas (una combinación de teclas por ejemplo).

Si por ejemplo, nuestro residente consiste en hacer un sonido determinado cuando produzca una combinación determinada de teclas, deberíamos hacer algo así (si tienes dudas, repásate el artículo y el fuente sobre los códigos SCAN en el CPV-01):

void main()
{
    oldint_tec= getvect (0x09);      /*guarda dirección original ISR teclado*/
    setvect (0x09, nueva_isr_tecla); /*instala nueva ISR teclado*/

    keep(0, (_SS + (_SP/16) - _psp)); /*Termina, pero queda residente, y
                    deja residente en memoria el número de párrafos de memoria
                    indicado en el segundo parámetro de la función. Lo puedes
                    poner a mano, o haciendo el cálculo: SS+(SP/16)-PSP.
                    SS+(SP/16) nos da la dirección de el final del
                    programa en memoria, y la dirección del psp es
                    también la dirección de comienzo del programa en
                    memoria. Partido por 16 para darnos el tamaño en
                    párrafos (1 párrafo=16 bytes). Si luego le sumas
                    algo por si las moscas, por seguridad, tampoco le
                    viene mal ;) */
}

void interrupt nueva_isr_tecla (void)
{
    Leo_códigos_scan_y_si_es_mi_combinacion_activo_altavoz();
    oldint_tec();             /*llama a la int original para tratar la tecla*/
}

Como ves, la clave está en poder entrar en ese trozo de código residente a través de una interrupción.

Ok, ya hemos hecho un residente básico, y esta filosofía se puede complicar más y más. ¡Vamos un poco más allá!

Vamos a complicar un poco más nuestro residente, y probablemente usemos
funciones que llamen a interrupciones del dos, querremos tener acceso a disco,
etc, y nos vamos a tropezar con uno de los grandes problemas:

El DOS no es reentrante
***********************

Como habrás deducido de otros capítulos del CPV, el DOS es una antigualla: primitivo, limitado, sin protecciones, ni multitarea, etc. Lo de la reentrada del Dos, en pocas palabras, significa que es incapaz de suspender una operación en curso, saltar a otro programa, y volver al programa original (lo que hace cualquier s.o. digno de ese nombre: unix, os/2, vms, etc).

Bueeeeeno, exactamente no es eso, es "parecido": imagínate que tenemos un programa en marcha, y lo interrumpimos. Si nuestro TSR no ejecuta llamadas al DOS (improbable, especialmente en lenguajes de alto nivel), no tendremos problema en volver al programa interrumpido. El problema está en que si el programa original estaba ejecutando una rutina del DOS, y nuestro TSR le interrumpe, y llama a su vez a una rutina del DOS, el sistema operativo cargará de nuevo los registros SS:SP con la dirección inicial de una pila interna. Si se trata de la misma pila que ya se usaba en la ejecución de la función interrumpida (y no se debe excluir esta posibilidad, puesto que el DOS no ofrece protecciones de este tipo) nuestro TSR modificará la pila que usaba la función original, con las catastróficas consecuencias que te puedes imaginar 8((  ¡¡Catacras!!

Por eso se dice que no es reentrante, porque cuando quiere volver a la ejecución de la función que interrumpimos, se va al garete.

Por tanto, lo que haremos será llamar a nuestro TSR cuando el DOS esté inactivo, cosa que sabremos consultando el INDOS flag, y la int 28h, de las que hablaremos ahora mismito. Por cierto, que Microsoft con su política absolutamente "transparente" 8-P no documenta ninguna de estas características 8(((.

El INDOS flag (aviso de actividad del DOS)
******************************************

Cuando el DOS está activo, realizando alguna operación, un flag (byte) en la memoria se pone a 1 (o más) volviendo a 0 si el DOS está inactivo (idle). La dirección de ese flag la conseguimos con la interrupción indocumentada 34h, que nos devuelve la dirección del flag en ES:BX (segmento en ES, y offset en BX).

En realidad el flag es en realidad un contador que cuenta la profundidad de anidado de llamadas DOS, es decir: si tiene el valor 0, no se está ejecutando ninguna llamada DOS (¡y tú, so golfo, aprovechas para activar tu TSR ;) ! ). Si tiene el valor 1, está ejecutando una función del DOS. Si tiene el valor 2, quiere decir que una función del DOS ha llamado a otra función del DOS (bastante usual, por cierto), y así sucesivamente, según se vayan anidando las llamadas a funciones del DOS.

La interrupción DOS-IDLE: 28h
*****************************

El INDOS flag es insuficiente para saber si el DOS está activo, ya que permanece activo por ejemplo cuando tu estás viendo el prompt del DOS (al fin y al cabo el DOS está activo 8-D ), por lo que también debemos consultar la int 28h, que es repetidamente llamada cuando el DOS está inactivo, haciendo bucles como un idiota (está haciendo E/S usando interrupciones del DOS).

Desviándola a una ISR propia, podremos saber si es posible interrumpir al DOS.

Lo malo es que esta interrupción sólo se activa si un programa hace entrada/salida de caracteres mediante interrupciones del DOS (la activan las 12 primeras funciones del DOS), y si un programa no las usa (más eficacia usando interrupciones del BIOS, o acceso directo a memoria de vídeo), no se activará la int 28h, y nuestro TSR no podrá interrumpir aunque sea teóricamente posible.

Conclusión: debemos consultar la int 28, y el indos flag, y en cuanto uno de ellos se active, sabremos con seguridad que está permitido el salto a nuestro TSR, debido a la inactividad del DOS.

Nuestro código por tanto, debe incluir algo parecido a esto, que es el esqueleto de nuestro programa (no te olvides de ver los fuentes de los programas de ejemplo salvapantallas y capturapantallas).

/****** Variables globales **************/

void interrupt (*oldint_28) (void);
char far *in_dos;                           /*Flag de actividad del DOS*/

void main()
{
  _AH=0x34;
  asm int 0x21;          /*int que devuelve un puntero (ES:BX) al indos flag*/
  in_dos=MK_FP(_ES,_BX);                    /*Apunta al indos flag.*/

  Cambia_vectores_interrupción();

  oldint_28= getvect (0x28);                /*DOS idle, ¡vago! */
  setvect (0x28, dos_pasivo);

  keep(0, (_SS + (_SP/16) - _psp));         /*Termina, pero queda residente*/
}

void interrupt nueva_isr_reloj(void)
{
  oldint_clk();                             /*La antigua isr del reloj.*/

  if (!*in_dos && peticion)         /*¿Hay petic. y el dos no hace nada?*/
     EJECUTA_NUESTRO_TSR_QUE_HACE_LO_QUE_SEA();
}

/*Cuando el DOS no hace nada, se pone a hacer bucles como un gili****as
(en el prompt por ejemplo), y se activa esta interrupción. Si la desviamos,
sabremos cuando está pasivo (idle)*/
void interrupt dos_pasivo(void)
{
  oldint_28();
  if (peticion)                      /*Si hay petic, y no está capturando*/
     EJECUTA_NUESTRO_TSR_QUE_HACE_LO_QUE_SEA();
}
 


La interrupción 13h: actividad de HD  (I/O en disco a bajo nivel)
*****************************************************************

Nuestro TSR ya está *casi* listo para poder ser llamado: hemos consultado la int28h, el indos flag, pero... queda un pequeño detalle a tener en cuenta.

Si interrumpimos un programa en ejecución que está haciendo una transferencia a disco duro o flexible, lo más seguro es que se vaya el sistema a la porra, y más si nuestro TSR accede al disco (y encima perderemos los datos del programa). =8-(

Si el programa interrumpido usa acceso a disco mediante las funciones del DOS, no habrá problema, ya que nos hemos asegurado de que el TSR no interrumpe mientras el DOS está activo, pero... ¡se puede acceder al disco a bajo nivel, mediante el BIOS!

Bueno... la solución es fácil: ¡lo de siempre!, desviamos la interrupción 13h (funciones de acceso a disco), y si está activa, no permitimos la activación del TSR, hasta que no se haya salido de la misma. En fin, solo es consultar un flag más.

  ......
  ......
  oldint_13= getvect (0x13);             /*Acceso al disco a través del BIOS*/
  setvect (0x13, disco_bios);

  keep(0, (_SS + (_SP/16) - _psp));      /*Termina, pero queda residente*/
}

void interrupt disco_bios (void)
{
  acceso_a_disco=1;
  oldint_13();
  acceso_a_disco=0;
}

Cuando examines los programas de ejemplo (si colaboras), P_CAPTUR.C (Capturapantallas) y P_SAVER.C (Salvapantallas)

Fíjate que esta precaución no está implementada...

¡Hágalo Vd mismo 8) !, ya ves que el mecanismo es sencillo, sólo permitir la activación del TSR cuando "acceso_a_disco" es 0 (false).

Acceso a disco
**************

Fale, ya tenemos nuestro TSR casi completo, pero... mejor que no intentemos hacer por las buenas un acceso al disco puesto que el DTA activo (Disk Transfer Area) NO es el correspondiente a nuestro programa, sino que sigue siendo el del programa al que hemos interrumpido.

Evidentemente, la solución está en guardar la dirección del DTA original del programa (función 2Fh de la int 21h, o getdta() ), guardarla, activar otro DTA para nuestro TSR (función 1Ah de la int 21h, o setdta() ), y cuando terminemos nuestras transferencias a disco, restaurar el DTA original.

La dirección del DTA original se obtiene mirando en la dirección 80h del PSP (Program Segment Prefix, una tabla que lleva asociado cualquier programa en ejecución, y que se puede obtener con la función 62h de la int 21h, o con _DTA)

Nuestra pila auxiliar
*********************

Cuando hablé de la reentrada, mencioné algo sobre la pila del DOS. Bueno, vamos a hablar un poquín de ella, y sobre todo de no desbordarla.

Cuando se activa una interrupción, el DOS automáticamente conmuta a una pila interna (enanita). De hecho, al DOS no se le ocurre que esa pequeña pila debe atender no solo a la interrupción que se ha activado, sino además a nuestro maravilloso TSR. Por tanto esa pila no nos sirve, necesitamos una mayor, que podemos definir tranquilamente, reservando un espacio de memoria del tamaño que decidamos, y hacer que SS:SP apunte a esa zona de memoria.

Un ejemplo de lo que podríamos hacer:

/* Captura la pantalla en crudo crudísimo, creando un fichero para la
   pantalla en sí, y otro para la paleta */
void captura_pantalla (void)
{
  asm cli;
  ss=_SS;                            /*Usamos la pila auxiliar, no la */
  sp=_SP;                            /* diminuta pila del DOS*/
  _SS=_DS;
  _SP=(unsigned) &pila[tam_pila-2];
  asm sti;

  AQUI VENDRIAN LAS OPERACIONES CON FICHEROS PARA CAPTURAR UNA PANTALLA.

  asm sti;
  _SP=sp;                            /*Restauramos pila del DOS*/
  _SS=ss;
  asm cli;
}

Bien, pues como estamos hartos de resetear, estamos de acuerdo en que ponemos una pila auxiliar, y desactivamos las interrupciones en el cambio.

El Buffer de caracteres:
************************

PARABOLA X-DD :  Erase una vez un TSR salvapantallas, en el cual, se apagaba la pantalla (pasado un cierto tiempo), y volvía a encenderse tras la pulsación de alguna tecla. Y había un programa del pentágono, que ejecutaba el programa "Destrucción del mundo" cuando pulsabas la tecla "D".

Resulta que se activó el salvapantallas, y alguien pulsó la tecla "D" para salir del salvapantallas. La tecla se almacenó en el buffer de caracteres, reapareció el programa "Destrucción", y éste tomó la tecla como suya, con lo cual el mundo desapareció.

Bueno, pues tras esta tontería, se esconde la necesidad en ciertos casos de conocer el buffer de caracteres, que tiene la forma de una cola circular, donde se van introduciendo los caracteres, según vamos pulsando (y pita cuando está lleno).

Se accede a la cola a través de un puntero cabecera, y otro de cola (primer y última posición, mírate el artículo sobre colas del CPV4). El puntero de cola apunta al próximo carácter que se devolverá cuando lo pida alguna función del BIOS o del DOS. El puntero de cabecera se almacena en la posición 0040:001A, y el de cola en 0040:001C. La cola puede almacenar 15 caracteres (más el CR), con lo cual ocupa 32 bytes (ya que cada carácter se almacena con un byte de código scan, y otro de carácter).

Podremos por tanto saber si se ha pulsado una tecla mirando los scan codes, o el buffer del teclado, según nuestras necesidades. Además, para eliminar la tecla (y que no se destruya el mundo), se podría hacer algo así:

  temp=peek(0x0040, 0x001c); /*Guarda dirección ultimo caracter buffer car.*/
  oldint_tec();              /*llama a la int original para tratar la tecla*/

  if (activo)
    poke(0x0040, 0x001c,temp);/*Machaka la tecla que pulsamos para salir del
                               del ssaver.*/

Es interesante conocer dónde se almacenan las variables que el BIOS usa para gestionar la int09h. ¡Seguro que te será de utilidad!

        Offset   Tamaño         Variable almacenada

        17h      1 byte         Estado de teclado
        18h      1 byte         Estado de teclado ampliado
        19h      1 byte         Código durante introducción ASCII
        1Ah      1 word         Siguiente carácter en el buffer del teclado
        1Ch      1 word         Ultimo carácter del buffer
        1Eh      16 word        Buffer de teclado
        80h      1 word         Dirección inicial buffer de teclado
        82h      1 word         Dirección final buffer de teclado

Cómo saber si mi TSR está ya cargado
************************************

Si cargo otra vez el TSR, no solo me va a ocupar otra vez más memoria, sino que encima me puede dar problemas (aparte de mayor lentitud) al activarlo, descargarlo de memoria, etc. Para saber si está cargado en memoria hay varios métodos, y no vamos a tratar todos.

El que he usado en los programas de ejemplo es muy efectivo, aunque no excesivamente elegante, y si miras la lista de interrupciones de Ralph Brown, podrás ver que la usan muchos TSRs comerciales (verás muchos "installation check" en la lista de Ralph).

Se trata de hacer una pseudofunción de la interrupción que usemos (la del reloj por ejemplo), de forma que si llamamos a la función con un determinado valor en algún/algunos regitro/s, nos devuelva un valor determinado en los registros.

Por ejemplo, podría saber que mi TSR está cargado, si al llamar a la interrupción 1Ch (reloj), con un valor en AX de 1 (por ejemplo), ésta me devuelva 2 en BX (por ejemplo).

En ASM es más fácil. En un LAN, tienes que tener en cuenta que los registros se guardan antes de una ISR, y se reponen después. Eso podemos solucionarlo de la siguiente forma:

  oldint_clk = getvect(0x1C);    /*Guarda dirección original ISR reloj*/

  _AX=0x1357;        /*Un código que me invento yo. Mi ISR devuelve el valor
                       0x7531 si ha sido instalado anteriormente el saver.*/

  oldint_clk();      /*Llamo a la interrupción que uso (reloj).*/
  if (_AX==0x7531)   /*Código que me devuelve mi isr si mi prog está ya
                       residente, ante una entrada de AX=0x1357*/
  {
    printf ("\n¡¡¡¡ tronko !!!! ¿te sobra memoria o qué?\n");
    printf ("El salvapantallas está ya activado, hombre.");
    exit(0);
  }

void interrupt nueva_isr_reloj(void)
{
   oldint_clk();    /* la antigua isr del reloj.*/

   asm{  mov ax,16[bp];
         cmp ax,0x1357;
         je  YA_INSTALADO;       /* EXPLICACION: */
  }
                        /* Compara el valor del registro AX con
                           0x1357 para ver si le estoy preguntando
                           desde el programa principal si está ya
                           activo el saver. En caso afirmativo, la
                           isr devolverá (en Ya_instalado) el valor
                           0x7531 en el registro ax.
                           ¿Por qué uso la pila?: pues porque el
                           compilador, salva todos los registros
                           en la pila justo antes de empezar el isr,
                           y los restaura justo al terminar, luego
                           si modifico el _AX, dentro de la isr, ese
                           valor, será posteriormente machakado. Por
                           eso modifico directamente el valor de ax
                           que se restaurará desde la pila al terminar
                           la isr.*/

        ..............
        AQUI VIENE LO QUE YO QUIERA (ACTIVACION TSR, ETC)
        .............

YA_INSTALADO:
   asm  mov ax,0x7531;  /*Tienes la explicación arriba*/
   asm  mov 16[bp],ax;
}

Hay otra forma más "standard", y algo más complicada de saber si mi programa está ya cargado en memoria. Es usando la interrupción 2Fh (Multiplexor). El DOS necesita el mux, como interfaz de comunicaciones con el mundo exterior de sus propios "TSRs" (print, doskey, share,...).

Llamando a la int 2Fh, con AH=0, y AL=CODIGO le preguntamos si ese código está libre para nuestro TSR (sería como el código que me he inventado en la pseudofunción anterior). Si me devuelve 0, está libre, y puede instalar mi propio controlador (es decir, no lo tiene ocupado otro TSR el código), que se encargará de devolver FFh (o algo distinto de 0) a otro programa que pregunte si ese código está ya ocupado.

                      INT 2F - DOS Multiplex Interrupt

                For more information see the following topics:

                                     +------ ERROR CODES ----
  INT 2F,0  Get installed state      ¦ 01  Invalid function
  INT 2F,1  Submit file              ¦ 02  File not found
  INT 2F,2  Cancel file              ¦ 03  Path not found
  INT 2F,3  Cancel all files         ¦ 04  Too many files
  INT 2F,4  Pause / return status    ¦ 05  Access denied
  INT 2F,5  End of status            ¦ 08  Queue full
  INT 2F,8  DRIVER.SYS support       ¦ 09  Busy
  INT 2F,2E Error Translation Tables ¦ 0C  Name too long
                                     ¦ 0F  Invalid drive

Por tanto, desviamos la interrupción (para variar), de forma que sólo reaccione a mi código (que determinamos al instalar el TSR, y que debe ser mayor que h). El controlador que instalamos, debe hacer lo siguiente:

   ¿AH=MI_CODIGO?
     -SI:
        ¿AL=0?  (alguien pregunta si está usado ese código?
           -SI:
              mov ah,FFh   (sí, ocupado).
        ¿AL=1?
           -SI:
              mov ax,cs   (devuelve segmento de código).
     -NO:
        -Llama a ISR 2F original.

La descarga del TSR de la memoria
*********************************

Mira, te pongo un ejemplo (sacado de los programas de ejemplo capturapantallas y salvapantallas), y ahora seguimos, que tengo la boca seca (¡una cañitaaaaa!)

IF hay_petición_de_descarga y el dos no hace nada...

void descarga_tsr(void)
{
  if ( (FP_OFF (nueva_isr_tecla) == peek (0x0000,0x9*0x4)) &&
           (FP_SEG (nueva_isr_tecla) == peek (0x0000,0x9*0x4+2)))
           /* Descarga permitida del TSR

              EXPLICACION: Si activas el capturapantallas y posteriormente
              ejecutas otro TSR que se active con una combinación de
              teclas, éste funcionará perfectamente, pero... si descargas
              el capturapantallas, el 2º TSR, tratará de saltar a mi ISR
              del teclado (que él cree que es la ISR original), y como no
              la encuentra, porque la hemos liberado, no se activa. Pero yo
              puedo detectar si otro programa (en ejecución o TSR) ha
              desviado la ISR del teclado: Busco en la tabla de vectores
              de interrupción (segmento 0, los 1024 primeros bytes) la
              que corresponde a la int 09h, y si no coincide con mi ISR,
              es que alguien ha cambiado los vectores de interrupción,
              con lo cual no es aconsejable descargar mi TSR */
  {
    setvect (0x1C, oldint_clk); /* restaura las ISR originales*/
    setvect (0x09, oldint_tec);
    setvect (0x28, oldint_28);

    puts (" ** CAPTURAPANTALLAS DESCARGADO **");
    freemem (_psp);   /* libera la memoria ocupada por el TSR.*/
  }
  else
    puts ("Descarga imposible: otro programa desvió la int 9h");
    peticion_descarga=0;
}

Como ves, la clave está en liberar la memoria (con freemem(_psp) en turbo C, o con la interrupción 49h del DOS), reponiendo previamente las direcciones de las ISR originales que hemos desviado. Y claro, haciéndolo solo si nuestras ISRs no han sido desviadas por algún otro programa tras la instalación.

                      INT 21,49 - Free Allocated Memory

           AH = 49h
           ES = segment of the block to be returned (MCB + 1para)

           on return:
           AX = error code if CF set  (see DOS ERROR CODES)

           - releases memory and MCB allocated by INT 21,48
           - may cause unpredictable results is memory wasn't allocated
             using INT 21,48 or if memory wasn't allocated by the current
             process
           - checks for valid MCB id, but does NOT check for process
             ownership
           - care must be taken when freeing the memory of another process,
             to assure the segment isn't in use by a TSR or ISR
           - this function is unreliable in a TSR once resident, since
             COMMAND.COM and many other .COM files take all available
             memory when they load
           - see  INT 21,4A

Para terminar
*************

Los TSRs son complicados de narices, y por lo general, hay que guardar una serie de precauciones, aunque si se trata de hacerlos cascar, seguro que lo logramos pese a todo:

- No recursividad

- Cuidado con la memoria dinámica, y las funciones que la usen (desde un printf a un fopen, por ejemplo).

- Recuerda que la pantalla hay que guardarla, y restaurarla cuando termine el TSR, si nuestro TSR tiene que mostrar algo en pantalla (lo habitual).

- Para guardar menos "pantalla" en memoria, puedes usar una ventana para las opciones del TSR.

- Si estás en modo gráfico, lo mejor sería guardar la pantalla a disco (en modo 13h 64k), y restaurar el modo de vídeo, y la pantalla tras la ejecución del TSR.

- Puede darse el caso de que entre la combinación de teclas para la activación del TSR, y el permiso para hacerlo, pase un tiempo respetable (lo habitual es que sea casi inmediato, pero por si acaso). Puedes establecer una variable "contador", que cuente el tiempo (desviando la int 08h) empezando cuando hacemos la petición, y si es un tiempo demasiado grande deseche la petición (para que no se active el TSR cuando ya no nos lo esperamos).

Programas de Ejemplo incluidos
******************************

    P_CAPTUR.C ------> Captura pantallas en modo 13h.
    VISU_RAW.C ------> Visualiza los ficheros capturados.
    P_SAVER.C  ------> Salvapantallas.

    Solo disponibles en la versión colaboración. ¡píllalos!

Por 500 pelillas, SE HONRADO, y colabora si te ha gustado el CPV.

Todos los extractos de programación del artículo han sido sacado de los fuentes que tendrás si colaboras ;) . La mejor manera de ver de forma práctica cada punto del artículo.

No pruebes el salvapantallas con win, o con programas que usen modo protegido, ya que por sus características no detectarán la pulsación de las teclas. Puedes probarlo con la mayoría de las aplicaciones DOS que tendrás por casa: BC, BP, Turbo C, Turbo Pascal, bluewave, front door, neopaint, pcgpe, orcad, pctools, harvard graphics, vpic, vcpv, el prompt del dos, etc...

Funcionan igual los programas bajo ms-dos, dr-dos y os/2.

Gracias a (¡ consulta sus libros 8) !)
**************************************

Herbert Schildt (Born to Code in C ).
Michael Tischer (PC Interno).

Ambos con unos apartados sobre TSRs muy majos: Schildt volcado al C, y Tischer separando las rutinas básicas (en ASM), y el programa en C o Pascal.

Para correcciones, comentarios, sugerencias, etc:
Francisco Priego 2:341/5.64 @FidoNet (Paco ¡¡P!!)



5.6.- Sonido Digital por el Speaker.

- Por Francisco Priego (Paco ¡¡P!!) -

SONIDO DIGITAL POR EL SPEAKER. Introducción:
********************************************

Hemos visto en lecciones anteriores del CPV la forma de generar sonidos con el pequeño altavoz de nuestro PC. Por fin llegó el momento de dejar a un lado los típicos pitidos, y hacer que en nuestros juegos haya explosiones de verdad, choques, disparos, y hasta voz... , todo lo que se te ocurra.

Lo ideal, claro, es tener una tarjeta de sonido, pero ante la falta de la misma en la mayoría de los ordenadores, podemos crear un buen clima en nuestros juegos si incluimos efectos de sonido digitalizados que se oigan por el altavoz, aunque no suenen demasiado fuerte. ¿Has visto el pinball fantasies por ejemplo? ¡vaya sonido!

En cualquier caso, la calidad del sonido dependerá muchísimo del altavoz de nuestro PC. En algunos, los ejemplos que verás a continuación, suenan casi como si se oyeran a través de una tarjeta de sonido, mientras que en otros el sonido es prácticamente inaudible, aunque por lo general, el resultado es bastante aceptable.

Comprueba tu mismo como se oye en tu ordenador el típico grito de mi querido Pedro Picapiedra. 8) Especifica "detección automática" cuando te lo pregunte.

Pulsa F2 para ejecutar el programa SPK-VOC.EXE YABBA.VOC    - Posibilidad no disponible en la versión HTML-

¡ consigue los fuentes colaborando !

El programa spk-voc.exe funciona con .VOC (preferible) o .WAV con muestras de 8 bits y un solo bloque de datos. También puedes oír otro tipo cualquiera de ficheros (.EXE .TXT, etc).

Requisitos previos
******************

Para comprender bien la lección, espero que tengas bien claro los temas ya tratados en el CPV que daré por sabidos, pero que siempre puedes consultar para acabar de entender los conceptos nuevos:

    - " Sonido ¿De qué va eso? "  (CPV Nº1)
    - " El sufrido PC-Speaker  "  (CPV Nº1)
    - " PIT 8253/8254 (timers) "  (CPV Nº2)

Y si quieres hechar un vistazo a la estructura de los ficheros .VOC (CPV Nº4) tampoco está de más, aunque no es necesario.

Cómo conseguir lo imposible (Rambo 7, nasío pa matá)
****************************************************

Cuando veíamos el artículo sobre el altavoz (speaker), veíamos que vibra a la frecuencia que deseemos, consiguiendo distintos sonidos variando la frecuencia de reproducción (incluso en segundo plano, como vimos en "sonido en background" (CPV Nº 2)).

Sin embargo, el sonido tiene dos características: frecuencia y... amplitud.

La amplitud, (el "volumen" a que se reproduce cada muestra) viene dada por la tensión presente en el altavoz. Si miras el altavoz de tu equipo de música, podrás ver como se mueve rítmicamente al ritmo de la batería por ejemplo. Se moverá más, cuanto más tensión haya en sus bornas.

El problema del altavoz de nuestro PC, es que no permite modificar la amplitud de ninguna manera, ya que solo tiene dos estados posibles: 0 voltios (reposo), o 5 voltios (máxima extensión posible del cono del altavoz).

Bien, pues como no podemos conseguir 1V, ni 2V, ni 2.7V, recurriremos a "algo" para conseguir el mismo efecto, recurriendo a lo típico en el mundo de los PCs: ¡los truquillos!

El truco del almendruco: como variar la amplitud del altavoz
************************************************************

Fácil: si tienes un 0 (reposo) en el altavoz, y de pronto metemos 5V, si viésemos la imagen del altavoz a cámara lenta, podríamos ver como se va "estirando", hasta conseguir una extensión completa, y cómo vuelve al reposo al poner un 0.

Bueno, pues si ponemos un 0V (tras poner 5V) cuando el altavoz lleva solo medio camino recorrido, conseguiremos el mismo efecto que si ponemos 2.5V. ¡hemos conseguido una magnitud analógica!

Experimentalmente, se sabe que el altavoz tarda aproximadamente 60uS (microsegundos) en alcanzar la plena extensión, con lo cual, si aplicamos 5V durante 30uS conseguiremos la mitad de la amplitud, si lo hacemos durante 15uS la cuarta parte, etc.

Por supuesto, esto no funcionará en los ordenadores portátiles cuyo altavoz es piezoeléctrico (son dos láminas de metal unidas), puesto que no tiene ninguna membrana que se estire y se encoja.

¿Y cómo mido yo esos tiempos?
*****************************

Pues sí, lo has acertado... con un timer, evidentemente.

Si quiero reproducir un archivo de sonido (sin compresiones) .VOC o .WAV por ejemplo, nostros ya sabemos que en formato crudo, consta solo de una cabecera donde entre otras cosas indica la frecuencia de muestreo, y unos bloques de datos, con un valor (amplitud) que tendrá el sonido en cada momento.

Pues para reproducir cada muestra (que es un dato de amplitud), meteremos una cuenta en el timer 2 (conectado al altavoz del PC), que será mayor cuanto mayor sea la amplitud del dato. Por tanto, el altavoz se estirará más, y oiremos esa muestra más fuerte.

Como el timer 2 es conducido por un reloj de frecuencia 1193180 Hz, la cuenta se decrementa en 1 cada 1/1193180 segundos = 0.83uS. Por tanto, para alcanzar una extensión plena (60uS), el timer 2 debe iniciar una cuenta con un valor inicial de 60/0.83 = 72.  Por encima de ese valor, el altavoz se saturará, y alcanzará la misma extensión máxima para cualquier valor superior.

La frecuencia de reproducción
*****************************

En lecciones anteriores, vimos lo que era el "muestreo": coger muestras (de sonido en este caso) a intervalos periódicos de tiempo, para "simular" la onda real de sonido (que es analógica, es decir, contínua).

Al efectuar ese proceso al grabar un archivo .VOC por ejemplo con una soundblaster, tenemos la opción de hacerlo a una frecuencia determinada (de 11Khz a 44Khz normalmente).

Para nuestros propósitos, lo mejor será un muestreo bajo (peor calidad), puesto que la reproducción por el speaker no nos permitiría apreciar la mayor calidad de un muestreo a mayor frecuencia, que nos conduciría inevitablemente a un mayor tamaño del fichero (y no son pequeños precisamente). Por supuesto, grabaremos en mono (ya que solo reproducimos por un canal), y con un tamaño de la muestra de 8 bits, que nos ahorrará el doble de tamaño que una de 16 bits, sin merma en la calidad del sonido.

Pues si hemos grabado ya nuestro fichero .VOC o .WAV, lógicamente tendremos que reproducir cada muestra del archivo a la misma frecuencia a la que lo grabamos, salvo que lo que queramos sea oír nuestra voz como si fueran los pitufos (agudo, a mayor frecuencia de reproducción), o como un cassette sin pilas (grave, a menor frecuencia de reproducción).

¿Y quién nos dicta el ritmo al que tenemos que reproducir cada muestra?
***********************************************************************

Pues alguien que nos avise a intervalos periódicos de que tenemos que meter un dato nuevo en el timer 2, es decir, alguien que nos vaya dando "ticks", al ritmo de los cuales vamos reproduciendo las muestras.

Ese "alguien", podría ser un timer ¿no? , y como el timer 1 no lo podemos utilizar porque está ocupado refrescando la dram, tenemos que recurrir al timer 0, reprogramándolo de forma que nos de la frecuencia a la cual tenemos que reproducir cada muestra.

Como el timer 0 trabaja a golpes de reloj de frecuencia 1193180 Hz, nosotros querremos que nos "avise" de que hay que meter una nueva muestra cada 1/F segundos, siendo F la frecuencia de reproducción. Por tanto, tendremos que meter una cuenta de 1193180/F en el timer 0 para conseguir que nos avise con un pulso (y generando una interrupción automáticamente ;) ) cada vez que llegue al final de la cuenta.

En C por ejemplo, estos son algunos de los pasos previos antes de empezar el bucle principal de reproducción:

  cuenta=(int)(1193180L / frecuencia);

  outportb(CTRL_TIMER,0x90);   /*programa timer2: 1 byte de cuenta,
                               modo0: genera 1 pulso al acabar la misma.*/
  outportb(PUERTO_ALTAVOZ,inportb(PUERTO_ALTAVOZ)|0x03);/*conecta cont2
                                                          y speaker*/
  outportb(CTRL_TIMER,0x36);   /*programa timer0: 2 bytes de cuenta,
                                 modo3: genera onda cuadrada, con una
                                 frecuencia dependiente de la cuenta.*/
  _CX=cuenta;
  outportb(TIMER0_(CLK),_CL);  /*empieza cuenta timer0.*/
  outportb(TIMER0_(CLK),_CH);

La interrupción 8 (reloj)
*************************

Bien, ya tenemos bastante experiencia en el desvío de la interrupción 8 a una rutina de atención propia (puedes ver el artículo sobre residentes en esta misma lección, o el "sonido en background" en el CPV Nº2 por ejemplo).

En nuestro caso, al alterar la cuenta del timer 0, para conseguir una frecuencia mucho mayor que los 18.2 Hz habituales, alteraremos la hora del PC, que se nos adelantará una unidad de medida universal (es decir, un huevo (el huevo se usa habitualmente en la medida de masas, pesos, tiempos, etc...)).

Para evitarlo, desviaremos la interrupción, a otra que NO llame a la ISR original (que solo mande el EOI), con lo cual se parará el reloj del PC durante los pocos segundos que dure la reproducción de nuestro sonido, y como tampoco nos vamos a morir por eso, pasamos de otras posibles soluciones.

En el programa, yo he aprovechado para meter en la ISR el dibujo de la gráfica durante la ejecución del sonido (dibuja un punto cada vez), pero lo mejor, para no distorsionar el sonido en ordenadores lentos (286 por ejemplo) es mandar solo el EOI al PIC, y dejarnos de mandangas.

Un poco de ASM  (la instrucción HLT)
************************************

Esta instrucción PARA el procesador al ejecutarla, y la CPU solo sale de este estado ante un reset, o una interrupción.

¡ahora verás para que lo vamos a usar! 8)

La idea global del programa (¡la clave!):
*****************************************

Bueno, ya tenemos casi todas las piezas de nuestro programa... ahora vamos a juntarlas. Es realmente fácil comprenderlo, en cuanto veas el siguiente pseudocódigo:

  -Carga datos en memoria.
  -Inicializa: desvía int8, averigua frecuencias, datos archivo, etc.
  -Programa timer 2 en modo 0, conectándolo al altavoz.
  -Programa timer 0 en modo 3 con dos bytes de cuenta.
  -Empieza cuenta del timer 0. (frec. de reproducción de cada muestra).

  DO
  {
      -PARA PROCESADOR HASTA INT (HLT)  (la frec. a la que se produce esa
                                         int viene dada por el timer 0)
      -CARGA NUEVO DATO EN EL TIMER 2.  (lo saco de la memoria).
  }
  WHILE (NO LLEGUEMOS AL FINAL DE LAS MUESTRAS).

  -Restaurar timers e int8.

Por desgracia, este programa no funcionará bajo OS/2 8'( , debido a que necesita toda la atención del procesador (por supuesto tampoco funciona bajo win).

Reproducción por debajo de 16Khz
********************************

Si reproducimos archivos muestreados por debajo de 16Khz, tendremos que tener en cuenta un desagradable efecto: el "tono de portadora".

Por encima de 16Khz, no debemos preocuparnos por ello, puesto que el oído humano no percibe esas frecuencias.

Este tono (un pitido a la frecuencia de reproducción) lo oíremos además de nuestro sonido, de forma que si reproducimos el grito de Pedro Picapiedra a 11Khz (la frecuencia a la que originalmente se muestreó), lo que oiremos será lo siguiente:

Cuando te pregunte, introduce lo siguiente:

            - Introducción manual de los datos.
            - ¿Frecuencia:     11000   (los 11Khz).
            - Amplitud (mute): 2
            - Sobremuestreo:   1

Pulsa F2 para ejecutar el programa SPK-VOC.EXE YABBA.VOC    - Posibilidad no disponible en la versión HTML-

¿has oído el pitido?  puajjjj, qué desagradable.

La solución: pues reproducir (en caso de frecuencias inferiores a 16Khz) al doble de velocidad. Y para que nuestro sonido no suene como los pitufos, lo que haremos será reproducir cada muestra dos veces, de forma que parezca que reproducimos a 11Khz en el caso de Pedro.

Esto es lo que hace el parámetro "sobremuestreo" cuando metemos manualmente los datos, los reproduce 1 vez (como en el caso anterior), o 2 veces para oír correctamente a Pedro.

Reduciendo la amplitud
**********************

Aprovecho para decirte que la "Amplitud (mute)" que pregunta el programa (por defecto 2) es una reducción del "volumen" de la muestra (mayor cuanto mayor sea el "mute"), y que puede ser necesario si el archivo tiene datos de mucha amplitud, que lleguen a saturar el altavoz (es decir, que llegue siempre  o casi siempre el cono del altavoz a su máxima extensión).

En el programa se hace lo siguiente: muestra=muestra >> mute (shr mute) es decir, dividir la muestra por 2 si mute=1, por 4 si mute=2, etc.

De esta forma nos aseguraremos de que el altavoz no se sature, aunque nos interesará el máximo volumen posible, sin llegar a saturar el altavoz (cuenta de 72 en el timer 2 como vimos antes).

Este parámetro es útil también, debido a que la medida de 60uS hasta la extensión máxima varía según el tipo de altavoz de que dispongamos. Lo normal será una reducción de 2.

Para finalizar
**************

Ya has visto lo fácil que puede ser dar un aspecto completamente distinto a nuestro juego.

Con una tarjeta de sonido y un poco de imaginación puedes grabar lo que quieras. Lo siguiente fue grabado en mi casa, con un micrófono viejo desguazado de una antigua emisora. ¡ y puedes hacer que suene Siniestro Total por tu ordenador ;) ! . Lo normal será grabar explosiones, choques, disparos, etc. Fíjate en el sonido de un coche dándose un trompazo a continuación:

Especifica detección automática.

Pulsa F2 para ejecutar el programa SPK-VOC.EXE CPV.VOC    - Posibilidad no disponible en la versión HTML-

Gracias a:
**********

Phil Inch por su GDM (Game Developers Magazine), sin el cual, no hubieras leído este artículo.



5.7.- Música de Fondo SB.

Más SBA.

Comenzamos aquí la segunda parte del tratamiento del sonido mediante la SoundBlaster. En este artículo trataremos el tema del sonido sintetizado FM, de las diferencias de este tipo de sonido respecto a los sonidos digitalizados, y de cómo podemos manipular y utilizar dichos sonidos para nuestros juegos. Comencemos...

Recordaréis de otros artículos la tabarra que os hemos dado con: ¿qué es el sonido?, naturaleza del sonido, ¿cómo se genera el sonido?,... etc. Bueno, pues aquí tendremos que empezar también explicando un poco que son los sonidos sintetizados.

En otros artículos hemos hablado de los sonidos digitalizados, os comentamos que dichos sonidos no eran más que una serie de muestras de valores tomados sobre la onda generada por el sonido real con una 'frecuencia de muestreo' determinada (si lo que os acabo de decir os suena a chino, podéis repasaros un poco lo que significaba todo esto en los anteriores capítulos dedicados al sonido en el CPV). La pega fundamental que tenían esto sonidos era que ocupaban muchisimo espacio en disco, más aun, si queríamos que el resultado tuviera una calidad aceptable.

Debido a esto, unos cuantos tíos muy listos comenzaron a investigar como se lo podrían montar para que los sonidos, en general, no ocuparan tanto. Y descubrieron que la forma de la onda de algunos sonidos, aquellos que son más o menos regulares, se podían definir en función de unos pocos parámetros. Se pusieron manos a la obra y descubrieron que teniendo dichos parámetros de un sonido, se podía llegar, mediante una serie de algoritmos muy complicados basados en la sumas y restas de ondas, a obtener la forma de la onda del sonido original, pudiendo reproducir dicho sonido sin necesidad de tener almacenada todas las muestras de datos.

Este es el fundamento del chip de FM (que aunque os suene chungo significa 'frecuencia modulada') que posee la SB. Dicho chip es capaz de generar, a partir de los parámetros que os he mencionado antes, la onda que nos permitirá escuchar el sonido original que a partir de ahora llamaremos 'instrumento'.

La lastima es que no todos los sonidos del mundo se pueden definir conforme a los parámetros anteriormente dichos, sólo aquellos que son más o menos regulares. Dicho requisito es cumplido por la mayoría de los instrumentos musicales, que son los objetivos más utilizados por la tecnología de síntesis. Pero bueno, basta ya de charla y vayamos al grano.

Ficheros '.CMF'

Existen numerosos formatos de ficheros orientados a la música sintetizada, los '.MID', los '.CMF', y tantos otros, pero en este capítulo hemos decidido centrarnos en los '.CMF' ya que es de los que más documentación existe, además de que son los que mas conocemos, ya que ya hemos trabajado con ellos. Para el manejo de dichos ficheros existe un driver que se instala residente en memoria, llamado 'SBFMDRV.COM'. Posteriormente explicaremos cómo se utiliza dicho driver, además de ver que parámetros y funciones tiene. Pero en esta primera parte, explicaremos cual es el formato de los ficheros '.CMF'.

Los ficheros '.CMF' tienen, como la mayoría de los ficheros formateados, una cabecera y un conjunto de datos con un formato determinado. La cabecera tiene la siguiente información...

              Bytes                    Significado

        ->    00-03                Identificador de fichero: Si el fichero
                                en cuestión es un '.CMF' deberá tener en
                                este campo la cadena "CTMF".

        ->    04-05                Número de versión: Número de versión del
                                fichero '.CMF'. Suele ser 1.10 (0A 01)

        ->    06-07                Offset de los instrumentos: Desplazamiento
                                respecto del inicio del fichero, de donde se
                                encuentran los parámetros de los instrumentos.

        ->    08-09                Offset de la Música: Desplazamiento
                                respecto del inicio del fichero, de donde se
                                encuentra el bloque de música. Este bloque
                                contiene, en un formato especial que no
                                entraremos a tratar, que notas deben ser
                                tocadas, con que instrumento, y con que
                                duración.

        ->    0A-0B                Golpes por corchea: Este parámetro junto
                                con el siguiente definen el 'tempo' de la
                                canción. Es decir, nos dicen la velocidad
                                con que la canción será ejecutada. Su valor
                                suele ser 120.

        ->    0C-0D                Pulsos de reloj por golpe: Número de pulsos
                                de reloj por golpe de ritmo.

        ->    0E-0F                Offset del título de la canción:
                                Desplazamiento desde el inicio de la canción
                                de la cadena de caracteres que define el
                                titulo. Este, como otros parámetros
                                posteriores no se suele usar, y su valor
                                suele ser (00 00).

        ->    10-11                Offset del nombre del compositor

        ->    12-13                Offset de las anotaciones: Offset de
                                alguna nota (dedicatorias, ... etc.) que el
                                autor ha querido poner a la canción.

        ->    14-23                Canales en uso: En los '.CMF' se admite la
                                posibilidad de tener hasta 16 canales a la
                                vez sonando. Estos 16 bytes pueden tener valor
                                00 o 01 indicando si el canal correspondiente
                                estará durante la canción activo o inactivo.

        ->    24-25                Número de instrumentos usados: El máximo
                                de instrumentos que podemos usar es de 128.

        ->    26-27                Tempo: Parámetro relacionado también con
                                la velocidad de ejecución de la canción.

        ->    28-               Resto del fichero '.CMF'

Bueno, pues esta es la información de la cabecera de los ficheros '.CMF'. Dentro de esta cabecera no todo es utilizado. Los campos de 'Offset del nombre del compositor' o 'Offset del título' no se utilizan casi nunca. Uno de los campos más importantes que sí se utiliza, es el de offset de instrumentos. En la posición del fichero señalada por este campo, nos encontraremos una tabla con los valores de los parámetros que antes os contaba de los instrumentos. Estos parámetros tienen nombres curiosos que seguro les sonaran a aquellos que estén medianamente relacionados con el mundo de los sintetizadores como son Sustain, Release, ... etc. Pero éste no es el tema que pretendemos tratar aquí, así que vamos a ver ahora como podemos hacer que nuestra SB 'toque' uno de estos ficheros ...

El driver SBFMDRV

Sé que muchos de vosotros, al igual que yo, estáis en contra del uso de drivers para vuestros programas, ya que no sabéis como esta hecho el driver por dentro y si surge algún problema (como, por otra parte, suele ser habitual en los drivers de Creative Labs) no hay manera de solucionarlo. Pero debido a que no tenemos tiempo de refinar las librerías que estamos haciendo para la SB, y que la gente no se termina de decidir a participar en asuntos como éste, nos vemos obligados a explicar por ahora estos drivers.

En particular el ejecutable que instala este driver se llama 'SBFMDRV.COM'. Y como su propio nombre indica, se dedica a manejar la FM (en particular los ficheros '.CMF') de la SB. El proceso de instalación es simple. Basta con ejecutar este fichero y el driver se instalará en una interrupción a partir de la 80h. Una vez instalado, nuestra tarea se reduce a llamar a dicho driver mediante una instrucción INT en ensamblador pasándole los parámetros necesarios en los registros del procesador, o utilizar los recursos del lenguaje que estemos utilizando para llamar a la interrupción.

Lo primero que debe hacer nuestro programa es detectar si el driver está o no instalado. Para ello basta con capturar el vector de interrupción (ver primeras lecciones del CPV) de sucesivas interrupciones a partir de la 80H. Dicho vector tendrá un 'segmento' y un 'desplazamiento'. Pues entonces deberemos ver si en la dirección 'segmento':0104H se encuentra la cadena 'FMDRV'. Si así es, es que el driver se encuentra situado en dicha interrupción, si no, tendremos que pasar a ver la siguiente interrupción. Si no hemos detectado dicha cadena antes de la interrupción XX, es que el driver no se encuentra residente en memoria y deberemos sacar un mensaje de error.

Una vez que sabemos que el driver está en memoria, ya podemos llamar tranquilamente a sus funciones mediante la interrupción que hemos detectado. Dichas funciones son las siguientes:

             Nombre de la función       Parámetros

        ->   Obtener versión de drv     Entrada:
                                                BX = 0
                                        Salida:
                                                AX = Versión del driver

Esta función solo es interesante si queréis saber que versión del driver estáis utilizando. Pero esto no debe tener la menor importancia.

        ->   Selec. byte de estado      Entrada:
                                                BX = 1
                                                DX:AX = Dirección del byte
                                                        de estado

Sin embargo, esta función si es muy útil ya que nos permite seleccionar como byte de estado una variable nuestra. Esta variable nos servirá, fundamentalmente, para saber si la canción ha terminado ya o no.

        ->   Selec. tabla de instr.     Entrada:
                                                BX = 2
                                                CX = Número de instrumentos
                                                DX:AX = Dirección de la tabla
                                                        de instrumentos

Esta función debe ser llamada con la dirección de la tabla de instrumentos antes de empezar a ejecutar una canción. Inicializa la tabla de instrumentos del chip FM para que cuando comience la canción, ésta lo haga con SUS instrumentos.

        ->   Seleccionar velocidad      Entrada:
             driver                             BX = 3
                                                AX = 1193180/PRG

Selecciona la velocidad del driver en la ejecución de la música. Donde el valor PRG son los pulsos de reloj por golpe. Valor tomado del offset 0C-0D de la cabecera del fichero '.CMF'.

        ->   Tocar Música               Entrada:
                                                BX = 6
                                                DX:AX = Dirección de la
                                                        música a ser tocada.
                                        Salida:
                                                AX = 0 -> si hubo éxito
                                                     1 -> si hubo fracaso

Comienza a tocar una música por la SB.

        ->   Parar Música               Entrada:
                                                BX = 7
                                        Salida:
                                                AX = 0 -> si hubo éxito
                                                     1 -> si no había música
                                                          activa

Para una canción que se esté ejecutando.

        ->   Resetear Driver            Entrada:
                                                BX = 8
                                        Salida:
                                                AX = 0 -> si hubo éxito
                                                     1 -> si hubo fracaso

Ojo, es necesario que esta función sea llamada antes de ejecutar ninguna canción  y antes de salir del programa.

        ->   Hacer pausa                Entrada:
                                                BX = 9
                                        Salida:
                                                AX = 0 -> si hubo éxito
                                                     1 -> si hubo fracaso

Hace una pausa en la canción para ser reanudada después con la siguiente función

        ->   Reanudar canción           Entrada:
                                                BX = 10
                                        Salida:
                                                AX = 0 -> si hubo éxito
                                                     1 -> si hubo fracaso

Reanuda una canción parada.

Bueno, hasta aquí la descripción del driver, ya no os queda más que probar con el hasta que le cojáis el truquillo.

Una cosa que quizás se nos pasó en el anterior artículo y que os queríamos dejar bastante claro. Los drivers como el CT-VOICE y el SBFMDRV son muy específicos de la tarjeta que tengáis instalada en vuestro ordenador. Así pues el driver de la SB Pro de 8 bits no es el mismo que el de la SB 16 con lo que lo más seguro es que si ejecutáis directamente la demo que metemos con estos artículos relacionados con la SB, no os funcione. Lo que tenéis que hacer lo primero es sustituir los drivers que suministramos nosotros con el capítulo por los vuestros, y así es posible que funcione. De todas formas si aun así no conseguís que funcione, no es culpa del programa, que lo único que hace es llamar a las funciones del driver. Es cosa de la estructura interna del driver, el sexo de los ángeles... en fin, ¿quién sabe?.

Bueno, una vez avisados, ya podéis ver la demo...

Pulsa F2 para ejecutar el programa Toca_CMF.    - Posibilidad no disponible en la versión HTML-

Ohhh, que detalle Luke, como se ve que sabes valorar los buenos programas y las buenas obras ;-)), gracias Luke Skywalker me has emocionado :''-)) que el CPV te acompañe. X''-DD

Una última cosa, la música FM tiene su propio chip dentro de la SB, y es completamente independiente de la salida de sonidos digitalizados. Lo que normalmente se hace en un juego es que mientras que los sonidos digitalizados solo se ejecutan cuando ocurre el suceso que los provoca, la música FM estará sonando constantemente. Para ello en el bucle principal debemos estar escaneando la variable de estado y cuando se ponga a cero, volver a iniciar la música, ya que este valor nos dice que la canción ya se ha acabado.

Bueno, espero que os haya gustado este articulillo sobre la SB. Dependiendo de vuestra acogida y de vuestras colaboraciones, haremos más artículos sobre la SB tratándola ya a nivel de puertos o no. Si de verdad tenéis interés en que esto siga adelante colaborad con nosotros... Hasta la próxima.
   


5.8.- Memoria EMS.

Estoy seguro de que muchos de vosotros lleváis esperando este artículo durante bastante tiempo. Seguro que ya os habréis topado más de una vez con el dichoso mensaje de 'No hay memoria suficiente' en vuestros programas y queréis aprender algún método para evitarlo. Pues ala, con alegría, comenzemos con un poco de historia (como siempre...).

En la prehistoria de la micro-informática (años 80), cuando los juegos venían en cintas y no ocupaban más de 60 o 70 Kb, salieron al mercado los primeros ordenadores personales que revolucionarón el concepto de informática doméstica. Dichas máquinas tenían la 'extraordinaria' capacidad de memoria de 512 Kb (más adelante 640) y se pensó que ésta, era más que suficiente ya que tardaría mucho en alcanzarse esa necesidad de memoria. Pues la cagaron bien ¿verdad?. ;-X

Antés de que se llegasen a dar cuenta de estos problemas, el éxito de los PC's fue enorme, se extendieron como la espuma, tanto a nivel empresarial como personal. ¿El secreto de este éxito?, LA COMPATIBILIDAD, un arma de doble filo como veréis posteriormente. En aquellos tiempos se creo la tan conocida familia de los PC - IBM y compatibles y se desarrolló cantidad de hardware y sobre todo software para esas máquinas, utilizando a diestro y siniestro esa organización de memoria de 640 Kb que a todas luces parecía más que suficiente. X-))) La tecnología avanzaba y se tenía capacidad suficiente para realizar arquitecturas y micros más potentes y en ese momento se tomo 'la gran decisión', en algún lugar de los estar-dos unidos, se reuniría un grupo de personas (que seguramente no tendrían n.p.i. sobre ordenadores) y alguien diría:

- Pues bien, ¿que hacemos con el viejo chip cuando saquemos este nuevo?   ¿Rompemos con todo y aprovechamos al 100 por 100 las posibilidades de éste?,   o ¿guardamos una compatibilidad que permita reutilizar todo lo que está   ya hecho? En nuestra nueva máquina, todo ira más rapido, funcionará mejor   y tal y tal...

Y UNA M..., diría yo, la pena es que no estaba allí; por aquellas fechas andaría yo jugando a las chapas. X-)) Al hacer que una familia de micros como los 80x86 sea compatible hacia atras, por un lado estás posibilitando que los programas antiguos puedan correr sobre las nuevas máquinas, pero por otro lado (y de ahí el doble filo) estás heredando todas las posibles restricciones que tenían las viejas máquinas. La peor de todas las limitaciones era la barrera de los 640 KB como se conoce en la jerga a la memoria convencional.

La tecnología seguía 'in crechendo' y las nuevas herramientas de desarrollo de software que aprovechaban a fondo los recursos de los micros, daban mil y una posibilidaddes para nuestras ya 'potentes máquinas', digamos 386. Los nuevos interfaces gráficos de usuarios (GUI) y las aplicaciones ofimáticas y demás, eran auténticas devoradoras de memoria y se hacía necesario una solución. ¡Una solución quiero! dirían las marujas. ;-)

Así que se tuvo que recurrir a los tan conocidos parches en el mundo de la informática, para taponar las diarreas mentales de aquellos primeros ilustrados. ;-) Lo que se decidió fue dotar al ordenador de una memoria accesoria, a la cual no se podría acceder con el direccionamiento convencional, ya que de esta forma se perdería la compatibilidad con los anteriores modelos, si no de alguna otra forma que veremos después.

Entonces surgieron dos tipos de memorias accesorias: la memoria 'expandida' (EMS) y la memoria 'extendida' (XMS). La diferencia entre una y otra está, principalmente, en el modo de acceder a ella.

En la primera, se reservaba un segmento completo (denominado 'marco de página de EMS') en la memoria convencional, de tal forma que cuando se pretendía acceder a una parte (página) de la memoria expandida existía un 'controlador de memoria expandida' que se traía la parte deseada al marco de página, de forma que ya era posible acceder a su contenido mediante el direccionamiento de toda la vida. Esta técnica se conoce como bank switching' y se definió bajo unos estándares llamados LIM (de Lotus - Intel - Microsoft, casi na' ¿verdad?)

En la segunda, (memoria extendida) era necesario entrar en el modo protegido del ordenador, para poder acceder a los datos. En este tipo de memoria accesoria, no estamos limitados a referenciar la memoria en páginas de como máximo 64 Kb, sino que podemos manipular bloques de cualquier tamaño y en su dirección física real (por encima del Mb de memoria convencional + memoria de sistema)

La memoria expandida no tuvo demasiado éxito, pese a que era mas fácil de utilizar que la extendida. Esta ultima si que se 'extendió', por lo motivos que os acabo de enumerar, y de hecho es la que más encontramos habitualmente en nuestro PC.

Muchos de vosotros diréis: - ¿Que casi no hay memoria expandida?. Entonces, ¿cómo es posible que yo tenga 2Mb de memoria expandida en mi PC ?".

Pues bien, esto es debido a que, como muchos programas empezaron a usar la memoria expandida en cuanto salió, cuando se paso de moda, los fabricantes decidieron proporcionar un medio para que se pudierá utilizar la nueva memoria extendida como expandida. Este es el objetivo de controladores como el EMM386 o el QEMM386, que son capaces de entrar en modo protegido, acceder a la memoria extendida, y depositarla en el segmento del que hemos hablado antes, como si de memoria expandida se tratase.

Pero bueno, me estoy saliendo un poco de madre hablandoos de esto, ya que mi objetivo hoy era la memoria expandida y no la historia. Por ahora ya sabéis , que se divide en páginas, que existe un segmento mediante el cual se accede a la memoria y que existe un controlador de memoria expandida al hay que dar ordenes para que coloque el bloque (o página) que deseamos en el segmento anteriormente mencionado. Pero ahora bien, ¿Cómo se le dan ordenes al controlador de EMS?. Bueno, pues esto se consigue a través de una interrupción.

   Si no recordáis el funcionamiento de las interrupciones software, es mejor que repaséis las primeras lecciones del CPV antes de seguir adelante con el capítulo, ya que para lo que sigue, es necesario conocer como funcionan a fondo. La interrupción que se utiliza para manejar la EMS es la interrupción 67h. Sus funciones nos permitirán mandar ordenes al controlador para que éste, aloje o desaloje páginas EMS, sitúe una página determinada en el segmento de EMS, ... y en general todo lo necesario para poder acceder a esta memoria. Sólo nos resta ya conocer las funciones de dicha interrupción, vamos con ello. Aunque no están todas, éstas son más que suficientes para el manejo
de la EMS.

Funciones de la interrupción 67h

- Reconocimiento del segmento de EMS: Esta función nos sirve para poder conocer en que segmento se cargarán las páginas que nosotros le pidamos al controlador. Suele ser el D000 o el E000 (de manera que las páginas se situarán a partir de la dirección D000:0000 o E000:0000 respectivamente. Esta función tiene los siguientes parámetros:

        ENTRADA --> AH = 41h          SALIDA --> BX = Segmento de EMS

- Número de páginas libres: Nos permite saber el número de páginas libres que tenemos de EMS. Esta función tiene los siguientes parámetros:

        ENTRADA --> AH = 42h          SALIDA --> BX = Número de Páginas libres

- Número de páginas totales: Nos permite saber el número de páginas totales que tenemos de EMS. Esta función tiene los siguientes parámetros:

        ENTRADA --> AH = 42h          SALIDA --> DX = Número de Páginas totales

- Alojar Páginas: Función para alojar un número de páginas de EMS. Nos devolverá un handle (manejador) que servirá para acceder a estas páginas. Esta función tiene los siguientes parámetros:

        ENTRADA --> AH = 43h          SALIDA --> AH <> 0 si hubo fracaso
                    BX = Páginas a               AH =  0 si hubo éxito
                         alojar                  BX =  Manejador de
                                                       Páginas

- Fijar Mapping: Así se le llama a la orden que le enviamos al controlador para que copie una página al segmento de página. Tiene los siguientes parámetros:

        ENTRADA --> AH = 44h
                    BX = Página Lógica
                    AL = Página Física

- Liberar Páginas: Función para desalojar unas páginas de EMS, tiene los siguientes parámetros:

        ENTRADA --> AH = 45h
                   DX = Manejador
                         de Páginas

Y hasta aquí todas las funciones que yo creo necesarias para manejar la EMS. Sólo hay que tener unas consideraciones en cuenta.

-> En los ordenadores que tienen un controlador instalado que 'simula' la memoria EMS como son el EMM386, y el QEMM, hay que asegurarse de que dicho controlador esta instalado. Para ello el proceso es el siguiente...

1.- Capturamos el vector de interrupción de la EMS. Dicho vector tendrá un segmento y un desplazamiento.
2.- Construimos una dirección de memoria cuyo segmento sea el del vector de interrupción y desplazamiento 0Ah (es decir segmento:000A)
3.- Miramos a partir de la dirección obtenida a ver si encontramos la cadena "EMMXXXX0".
4.- Si la encontramos es que el controlador está instalado. Si no, pues no.

-> Hay que tener mucho ojo, y desalojar las páginas de EMS que vuestro programa aloje, ya que si no, nos podemos encontrar con que todas las páginas que no hemos desalojado se han quedado 'volando' y no se podrán reutilizar.

-> Compiladores como el Borland C 3.31, cuando compilas desde el editor, suelen coger toda la memoria expandida para ellos, de manera que tu programa no podrá alojar ni una sola página. La solución es cambiar las opciones del entorno adecuadamente y/o ejecutar el programa desde fuera del editor.

-> El tamaño de las páginas es, en general, de 16 Kb. Hay que tener en cuenta eso a la hora de alojar las páginas, y de calcular el espacio requerido.

-> La EMS se suele utilizar para cosas que ocupan gran espacio y que no se ejecutan constantemente. Un claro ejemplo son los sonidos digitalizados o los 'frames' de las animaciones. Es un error, a no ser que no tengamos mas remedio, utilizar la EMS para almacenar obejetos como los sprites que estamos utilizando en cada momento de nuestro juego.Para mayor rapidez, conviene tenerlos siempre en memoria principal.

La siguiente demo, ilustra sobre la utilización de la EMS para el almacenamiento de gráficos.

Pulsa F2 para ejecutar el programa DEMO_EMS.    - Posibilidad no disponible en la versión HTML-

Bueno, pues nada más por ahora, la memoria extendida es más complicada de utilizar, aunque si se tiene algún controlador de XMS (memoria extendida) instalado (tipo HIMEM.SYS) su funcionamiento es muy parecido. Pero esto es otra historia que debe ser contada en otro momento... Bien por nosotros o bien por algún colaborador que se anime. Hasta la próxima.
 


5.9.- Técnicas Avanzadas. RAY CASTING.

                       - por José María Peña Sánchez -

  - INTRODUCCION-

  El Ray Casting es una técnica relacionada con el Ray Tracing en el
  sentido en que ambas lanzan rayos para determinar el objeto con el
  que colisiona, y asi calcular el color correspondiente a una serie
  de pixels de pantalla; pero se diferencia de ella en que por medio
  de una simplificación del mundo a representar nosotros no tenemos
  que lanzar un rayo para cada pixel sino que mediante la proyección
  de un rayo obtenemos sufiente información para representar una serie
  de pixels de nuestra ventana de trabajo.

  La causa de la aparición de esta técnica tiene lugar en principal
  medida en el entorno de los juegos de ordenador, a diferencia del
  Ray Tracing que en un principio se originó en el ambito de las
  Artes Gráficas (publicidad, realidad virtual, ...). El primer juego
  conocido por todos que utilizó esta tecnica fué el WOLFSTEIN, que
  causó una autentica revolución en el mundo de los videojuegos,
  finalmente el autentico bombazo fué el DOOM, que mediante una mejora
  considerable de las técnicas del primero y unas texturas más
  trabajadas, nos dejó gratamente impresionados. En esta introdución
  al Ray Casting vamos a exponer los rudimentos del WOLFSTEIN y en
  sucesivas entregas iremos promocionando hacia las técnicas del DOOM.

  - RAY CASTING -

  La aparición del Ray Casting fue debida a la necesidad de poseer un
  grado de realismo similar al del Ray Tracing pero que requiera mucho
  menos tiempo para generar una imagen, con la única restricción de
  realizar una simplificación del mundo que deseamos representar, que
  en nuestro caso será:

  - Solo existen paredes planas de tamaño fijo (con texturas) con una
    distancia entre techo y suelo tambien fija.
  - Los objetos estan solo a un nivel.
  - Los movimientos permitidos son girar y avanzar.
  - No se puede mirar hacia arriba o hacia abajo, ni se permite ladear
    la cabeza.

  Los beneficios de esta simplificación se pueden resumir en que para
  representar nuestro mundo no necesitamos un sistema de coordenadas
  tridimensional, sino que podemos representarlo sobre un plano de 2
  dimensiones. De forma que las posibles intersecciones son calculadas
  mediante operaciones más simples. Así, los objetos como las paredes que
  antes eran representados como rectángulos sobre planos en el espacio,
  ahora pueden ser representados dentro de una matriz de dos dimensiones
  como un único dato.

  Antes hemos dicho que el principal beneficio de esta técnica es que
  mediante la proyección de un rayo podemos determinar numerosos pixels,
  que en nuestro caso serán los correspondientes a una columna de nuestra
  pantalla. Según las restricciones antes enunciadas, sabemos que para
  cada columna solo hay un muro que se represente en ella, de forma que
  una vez calculado el muro que antes intersecciona con un rayo, los demas
  rayos de la columna impactaran o en el suelo o en el techo o en el mismo
  muro. Esto significa que para lo que en Ray Tracing utilizabamos 200
  rayos (uno por fila de cada columna) y con cada uno realizabamos
  operaciones de intersección en 3 dimensiones, ahora lanzamos un único
  rayo con el que calculamos intersecciones sobre 2 dimensiones.

  Así los pasos a seguir son:

  1) Generar un rayo para cada columna.
  2) Con cada uno de los rayos calcular, la única pared con la que podemos
     colisionar (intersección en 2D).
  3) Por último, de los datos retornados por la función anterior, calcular
     el color de los pixels de esa columna.

  1) GENERAR RAYOS:

  Esta función no tiene mayor misterio, pues unicamente consiste en,
  conociendo la dirección en la que mira el personaje, dividir el angulo
  de visión en tantas partes iguales como columnas tengamos a cada lado,
  es decir 160 hacia la izquierda y 160 a la derecha.

  Debido a que esta función no tiene mucha complejidad, en caso de tener
  dudas, mirad los comentarios contenidos en el código.

  2) COLISION CON PARED:

  Esta función está muy relacionada con la representación escogida para
  los elementos del mundo. Yo he optado por representarlo en una matriz de
  32 x 32 elementos: si en la posición (i,j) tiene un cero indica que
  no hay paredes en ese cuadrado, y si hay un número distinto de cero es
  que está ocupado por un bloque.

                                        j
                                      i +----+ i+1
             Matriz[i,j]!=0   ===>      |    |          - + | :paredes
                                        +----+
                                        j+1

  De forma que un mapa en esta representación no es más que:

               11111111111111111
               1               1
               1 222222 222222 1
               1 22222   2222  1     NOTA: Asumimos que los blancos
               1 22222   2222  1           son ceros.
               1 222222222222  1
               1  22222222222 11
               1   2222222222 11
               3 3  222222222 11
               3 3            11
               33311111111111111

  Un buen algoritmo para mapas pequeños es: partiendo desde la posición del
  personaje y avanzando segun la dirección del rayo, ir chequeando cada una
  de las casillas por las que pasemos empezando por la del personaje y
  alejandose de él.

  i                                                     ALGORITMO
j 1     2     3     4     5     6     7     8  A)El Primer paso consiste en
1 +-----+-----+-----+-----+-----+-----+-----+    determinar si la casilla que
  |   x |     |     |     |     |     |     |    ocupa el personaje tiene o
  |     \     |     |     |     |     |     |    no paredes. Si es así ya
  |     | \   |     |     |     |     |     |    tenemos la intersección.
2 +-----+---\-+-----+-----+-----+-----+-----+  B)Sino, calculamos si desde
  |     |     \     |     |     |     |     |    esa posición y en la
  |     |     | \   |     |     |     |     |    dirección del rayo cortamos
  |     |     |   \ |     |     |     |     |    antes en el eje i o en el j,
3 +-----+-----+-----\-----+-----+-----+-----+    el menor indica por cual
  |     |     |     | \   |     |     |     |    lado de la casilla sale el
  |     |     |     |   \ |     |     |     |    rayo, si la casilla contigua
  |     |     |     |     \     |     |     |    por dicho lado está ocupada
4 +-----+-----+-----+-----+-\---+-----+-----+    termina el algoritmo, en caso
  |     |     |     |     |   \ |     |     |    contrario continuamos con el
  |     |     |     |     |     \     |     |    paso B) para buscar la
  |     |     |     |     |     | \   |     |    siguiente casilla.
5 +-----+-----+-----+-----+-----+-----+-----+

  Una vez expuesto el algoritmo, calcular una intersección sobre uno de estos
  mapas no parecería nada dificil, pues solo se puede suponer un problema de
  transformación de tipos, ya que nuestro heroe se mueve por posiciones
  continuas (números reales) y los valores de los ejes j e i son posiciones
  discretas (números enteros), que en un principio se nos podría ocurrir pasar
  los ejes a reales y operar las intersecciones en reales, pero si operamos
  con coma flotante las operaciones son más lentas y hay que tener en cuenta
  que este proceso lo tenemos que hacer en cada pantalla para todas las
  columnas y en cada una varias veces, de forma que si ralentizamos este
  proceso, el programa en conjunto será mas lento.

  Aceleración del cálculo de intersecciones:

   -Enteros: En lugar de trabajar en coma flotante usaremos números enteros
             tanto para los ejes i y j como para la posición del muñeco,
             pero como no causa una buena impresión que al avanzar solo
             una vez, saltemos de una casilla a otro y que los giros sean
             siempre de 90 grados, como muchos juegos de rol para ordenador
             tienen (ej: EYE OF BEHOLDER); nosotros tendremos que multiplicar
             nuestros enteros por un cierto número para 'simular' los reales,
             es decir que dentro de una casilla podamos estar en multiples
             posiciones, y si avanzamos o giramos, lo hagamos de poco en
             poco, cuanta mayor precisión queramos dar a estos números, mayor
             tendrá que ser el número que los multiplique, y para que este
             número pueda ser bastante alto el rango de los números ha de ser
             bastante amplio, por lo tanto usaremos enteros largos (long int)
             que tienen 32 bits, y que si usamos una compilación para 386
             podemos usar los registros extendidos y la aritmética de
             32 bits.

   -Desplazamientos: Como vamos a usar números multiplicados por una cierta
             cantidad M, si en el transcurso del programa multiplicamos
             dos números con dichas caracteristicas el resultado final estará
             multiplicado por M al cuadrado; problema que se repite con las
             divisiones, de forma que para mantener las unidades en cada
             multiplicación al resultado tenemos que dividirlo entre M y en
             cada división multilicarlo por M; por lo tanto a la hora de
             elegir M debemos tener en cuenta que las multiplicaciones
             o divisiones serán frecuentes, y la elección de números como
             1000 o 10000 que nos son comodos a los usuarios, NO son acertadas
             porqué a la máquina una mutiplicación por un número potencia de
             10 le lleva un tiempo respetable, y si es una división no
             digamos; la mejos solución es hacer coincidir ese número M con
             alguna potencia de 2, porqué la máquina para mutiplicar por 2 a
             la n solo tiene que desplazar sus bits a la izq n posiciones.
             En general a la hora de evaluar las operaciones que un algoritmo
             tiene que usar, hay que evitar ante todo raices cuadradas, los
             algoritmos o las operaciones trigonométricas (senos, cosenos)
             porqué se realizan mediante funciones de librería MUY LENTAS,
             tampoco son muy buenas las divisiones, que se tienen que usar
             sólo cuando no hay mas remedio, las multiplicaciones casi igual,
             pero son más frecuentes y más rápidas que las anteriores, y
             sobre todo fomentar las sumas, las restas, los incrementos y
             los desplazamientos y rotaciones de bits.

  3) DIBUJAR LA COLUMNA:

  El principal problema de este apartado consite en dividir la columna
  en 3 porciones, una ocupada por el color del suelo, otra por el del
  techo y una tercera por la textura de la pared.

  El criterio utilizado para realizar la división viene dado por la
  distancia de corte con el muro y otros parámetros de altura de techo
  y personaje. El primero nos puede salir de la función que determina
  los cortes con los muros, sin ninguna operación extra, y los otros
  son parámetros constantes o variables del programa.
                              ._
                           .  : |       La forma de calcularlo consiste
                        .     : |       en determinar que proporción de P
  TECHO              .        : |       correspondiente a Pared, Techo y
__________________.___________: |       Suelo:
 |             .              | |         P = 2 (T sen(fi) / cos(fi))
 | ___ _    .                 | |       o lo que es lo mismo:
 | |  (_).----------T---------| P _      P = cte T
 | |   |    .)fi              | | |     la proporción de pared en P es
 H h/--+--\    .              | | |       Pared = H / P
 | |   |          .           | | |   la de suelo
 | |  / \            .  PARED | |P/2      Suelo = (P/2 -h) / P
 |_|_|___|______________._____|_|_|_    y el resto Techo
                           .  : | |
  SUELO                       :_|_|

  Una vez calculadas las proporciones se empieza por uno de los extremos,
  por ejemplo el superior, se comienza a poner el color del techo,
  (en otras entregas enseñaremos como añadir texturas a techos y suelos)
  hasta la proporción dada de la ventana de trabajo, al llegar a la
  pared dividimos los puntos que tendrá su proporción de la ventana de
  trabajo entre los que tiene la textura del muro y esa cantidad nos indica
  cada cuantos puntos del sprite de la textura hemos de coger una muestra
  y ponerla en el pixel (escalado de la textura), y el resto se pone del
  color del suelo.

  Bueno, una vez explicada la teoría pasamos al ejemplo práctico. A
  continuación podéis ejecutar el programa demostración, donde se pone
  en práctica todo lo comentado, si estáis colaborando con el CPV también
  podréis examinar el código (en C) de donde espero, aprendáis mucho:

Pulsa F2 para ejecutar el programa RAYCAST.    - Posibilidad no disponible en la versión HTML-

  - RESUMEN -

  Espero que este primer paso al Ray Casting os haya despertado la
  curiosidad, y sigais con atención futuras entregas del CPV, en las
  que trataremos técnicas más avanzadas y potentes de Ray Casting que
  nos dirigiran hacia efectos tan vistosos como el %C¥DOOM:

                         - Ubicación de SPRITES
                         - Aceleración del Trazado
                         - Varios niveles de juego
                         - Efectos especiales de Ray Tracing:
                              Luces, Niebla, Agua, ...

  NOTA: Estos no son los únicos algoritmos de Ray Casting, ni siquiera
  los mas rápidos, pero mantiene un buen equilibrio entre velocidad y
  sencillez que los hacen fáciles de entender. Si tenéis alguna sugerencia
  o duda, podréis encontrarme en:

                                       E-Mail:
                                            jmpenya@clio.ls.fi.upm.es
                                            chema@baccara.ls.fi.upm.es
                                            a910347@zipi.fi.upm.es

  O también, podéis mandar un mensaje a mi nombre a las direcciones de
  JASM o BMP, (consultad los créditos F4).

  AGRADECIMIENTOS: En esta ocasión si tengo algo que agradecer a mi hermano
  Luis que esta vez si arrimó el hombro.
 
 


5.10.- Fractales.

Buscando entre mis apuntes encontré un tema que puede resultar interesante tratar, los gráficos recursivos, interesante por tres motivos: Primero, nos servirá para explicar la recursividad (un método de programación, poco utilizado, pero muy potente), conoceremos una unidad semi-desconocida del Pascal, la Graph3 y por último y quizás más importante, servirá como una introducción al mundo de los fractales tan de moda últimamente, que será tratado en un próximo capítulo.

Empecemos viendo que es eso de la recursividad. La recursión es el proceso por el cual un procedimiento o función se invoca a si mismo dentro del código que contiene. La condición indispensable para este proceso es que exista una condición de salida en el procedimiento para que éste, no se llame indefinidamente y se produzca irremediablemente el desbordamiento del stack y con ello la petada de nuestro sistema :-( Una cosa difícil de entender con la recursividad es que, cuando se cumple la primera vez la condición de salida, el proceso no se termina ahí, sino que lo hará cuando el resto de "copias" de nuestro procedimiento también cumplan su condición de salida. A ver, primera parada para entender esto, un ejemplito o mejor dicho el ejemplito ;-)

Imaginaros que queremos calcular el factorial de un número, es decir que dado un número n, o que leches, dado el número 4 queremos calcular el resultado de la multiplicación 4 x 3 x 2 (x 1) = 24 = 4! (factorial de 4). La definición matemática del factorial más próxima al concepto de recursión (y ahora si que tengo que usar letras X-), sería:

                                  { Si n = 0 entonces n! = 1
   Factorial de n (n > 0) -> n! = {
                                  { Si n <> 0 entonces n! = n * (n - 1)!

Como veis en esta definición de factorial, en el proceso de cálculo del mismo se emplea LA PROPIA DEFINICION de factorial, parece una contradicción o una locura, pero es así. El hecho de decir "Pues una patata es...  una patata que nace en la tierra..." se opone totalmente a la forma de pensar tradicional donde la explicación a cualquier problema se busca en todos los sitios menos en el propio problema, pero por ello no deja de ser real, de hecho lo que la hace real esta definición es la condición de salida indispensable de la que os hable antes, que provocará la terminación del proceso y que según la definición del factorial, se alcanzará siempre. Una función en Pascal que implemente esta definición sería simplemente:

  function n(a:integer):integer;
  begin
   if (a = 0) then               {Condición de salida}
    n:= 1
   else
    n:= a * n(a - 1);
  end;

Habréis observado el estilo conciso y elegante de programación que rodea a la recursividad. Bien, veamos ahora el proceso de ejecución para n(2)

               n(2)
 ------> +--------------+               n(1)
         ¦n(2):=2 * n(1)¦ ------> +--------------+               n(0)
         ¦  ........    ¦ <+      ¦n(1):=1 * n(0)¦ ------> +-------------+
         ¦ n(2):=2 * 1  ¦  ¦      ¦  ........    ¦ <+      ¦ n(0):=1     ¦
         ¦              ¦  ¦      ¦ n(1):=1 * 1  ¦  ¦      ¦             ¦
         ¦   n(2):=2    ¦  ¦      ¦              ¦  ¦      ¦  Fin de     ¦
         +--------------+  ¦      ¦   n(1):=1    ¦  ¦      ¦   recursión ¦
          ¦                ¦      +--------------+  ¦      ¦             ¦
                           ¦        ¦               ¦      +-------------+
      Fin de Programa      +--------+               ¦        ¦
                            n(1) = 1                +--------+
                                                     n(0) = 1

Como veis el fin de la recursión queda "muy lejos" del fin de programa, ya que éste, se produce cuando todas las copias de la función han obtenido sus valores de retorno y pueden continuar su ejecución. Con este ejemplo, espero que hayáis entendido el proceso de recursión.

Ahora vamos a seguir trasteando pero, ufff, dejamos las matemáticas y pasamos al mundo del pixel. A la vez que os vamos a enseñar, dos de los gráficos recursivos mas conocidos, os mostraremos la unidad del Turbo-Pascal, Graph3 que contiene algunas funciones muy interesantes.

Todos habréis oído hablar del Logo, aquel lenguaje de programación orientado a la educación infantil y a la inteligencia artificial, pues bien, Pascal implementa en esta unidad las funciones gráficas básicas de las que constaba el Logo. Estas instrucciones son como ordenes que se le dan a un cursor (en su lenguaje original con forma de tortuga) el cual reconoce y obedece. Las ordenes son del estilo de adelante (forwd), atrás (backw), gira a la derecha (turnright), ocúltate, muéstrate... Si queréis obtener todas las instrucciones y su descripción utilizar la ayuda del TP (que está muy bien por cierto) ;-) Nosotros como ejemplo, hemos utilizado estas instrucciones, en un par de funciones recursivas que generan dos de las curvas gráficas más antiguas y conocidas:

Pulsa F2 para ejecutar el programa GrafRecu.    - Posibilidad no disponible en la versión HTML-

A primera vista no encontraréis una utilidad especial a estas imágenes, pero si habéis sido buenos y con ello, habéis recibido los fuentes del CPV, observaréis que generar estas gráficas no cuesta casi ni diez instrucciones y no quiero ni imaginar lo que costaría generarlas con una programación secuencial tradicional. Estos gráficos, cuyo origen se remonta a principios de siglo, son los abuelos de los fractales y de las técnicas gráficas más actuales. Por ejemplo, la generación de montañas fractales, como las que habréis sobrevolado con vuestro helicóptero Comanche o las que observáis por la ventana al mirar por las fortalezas del Doom, se construyen de una manera similar a los triángulos de Sierpinsky, sólo que en aquellas, los triángulos (mucho más pequeñitos) se dibujan con una dimensión más, dándoles profundidad, y se les añade color y sombras para proporcionales una textura adecuada.

Sin irnos a técnicas tan complicadas como las que os hemos nombrado, podemos ofreceros otro efecto más sencillo pero igualmente sorprendente como el Plasma Fractal. Este efecto se origina, al igual que los gráficos anteriores, a través de tratamientos matemáticos (antes giros y geometría, ahora aritmética simple) y de un proceso recursivo, de ahí su apellido 'fractal'.

El proceso de construcción de un plasma consta de tres partes bien diferenciadas:

- Generación de la paleta. Como veréis más adelante una de las cosas más llamativas de un plasma es su bonito colorido y la cadencia de colores. Como todo lo en la vida, para conseguir algo bueno, es necesario un tratamiento individualizado y por ello la construcción de una paleta propia es algo indispensable en este efecto. Tenemos dos opciones, la más simple, aunque parezca mentira es, generarnos por código una paleta a medida. La otra opción es diseñarse la paleta mediante algún programa de dibujo que permita la construcción de paletas gradiente (escala de colores semejantes en secuencia) y salvar la misma, pero si sois un poco muñones (como es mi caso) con herramientas avanzadas, mejor escoge la primera opción :-)

- Construcción del fondo de pantalla. Este es el proceso principal del programa. En él, mediante procedimientos recursivos generaremos, (punto a punto y para toda la pantalla) una distribución de colores que junto a la rotación de la paleta, origina el efecto 'plasma'. ¿Cómo se generan todos esos puntos? pues muy sencillo.

Dibujamos 4 puntos de un color aleatorio de nuestra paleta, uno en cada esquina de un bloque rectangular inicial, generalmente las esquinas de la pantalla, suponiendo que estamos en modo 13h ¿Cómo no? ;-) las coordenadas del bloque serán (0,0,319,199).

Una vez definida nuestro rectángulo pasamos a dibujar 4 nuevos colores en la mitad de cada lado del polígono. El color a poner nos lo dirá una fórmula matemática que es el alma del plasma, la fórmula es "rarilla", esta más o menos estandarizada y no tiene ninguna explicación especial, simplemente es una relación entre la distancia absoluta entre las dos esquinas, un valor aleatorio, una cte y un par de factores más, pero a nosotros no nos tiene que preocupar, un buen día alguien la parió y oye, funciona.

Cuando tenemos los 4 nuevos puntos, calculamos un nuevo punto justo en el centro del rectángulo original, esta vez el proceso de cálculo si es sencillo, simplemente calculamos la media aritmética de las 4 esquinas iniciales y ya está. Bueno, os pongo un esquema del estado inicial de la pantalla, dibujado con la mejor y más model-na herramienta del mundo, el Ctrl + Alt + (un numerillo) X-)))

         (x1,y1)                  fórmula rara ;-)             (x1,y2)
            *--------------------------*--------------------------*
            ¦                          |                          ¦
            ¦                          |                          ¦
            ¦                          |(x,y) -> Med.Aritmética   ¦
   f.rara ->*--------------------------*--------------------------*<- f.rara
            ¦                          |                          ¦
            ¦                          |                          ¦
            ¦                          |                          ¦
            *--------------------------*--------------------------*
         (x2,y1)                    f.rara                     (x2,y2)

Como veis, llevo 30 líneas escribiendo para enseñaros a hacer esto, dibujar 9 puntos en pantalla, bieeeennnnn, fiu-fii, plas-plas, oeee-oe-oe-oe. Ya sé, ya sé, que os parece una tontería pero antes de colgarme, fijaros en un detalle. Con este proceso se nos han formado cuatro nuevos rectángulos más pequeñitos y .... ¿que pasaría si repetimos el proceso anterior para cada uno de los 4 rectángulos formados? Pues os lo diré, que estaremos generando el plasma.

Como el proceso para cada sub-rectángulo es exactamente el mismo que para el original, la programación recursiva se hace aquí poco menos que imprescindible. Lo único a decidir es ¿Cual será nuestra condición de salida? Pues bien para asegurarnos de que el proceso de generación termine, daremos fin a la recursión cuando la distancia entre los puntos de las esquinas sea menor que dos, osea, que los puntos sean consecutivos.

- Rotación de la paleta. Una vez generado el fondo, el último paso es la realización del efecto plasma en sí. Sin este paso, lo único que tendríamos sería un mogollón de puntos de colores, aparentemente sin relación, que no hacen absolutamente nada, pero... ¡oh! sorpresa, si nos ponemos a rotar nuestra paleta original pues ocurrirá una cosa como lo siguiente:

Pulsa F2 para ejecutar el programa Fx-Plasma.    - Posibilidad no disponible en la versión HTML-

Bien ya habéis visto el plasma y... diréis, ¡Guauuuu! que fuerte. Pues sí, es sencillamente genial. El porqué del nombre de plasma me imagino que os lo habréis imaginado al ver el efecto y si no, haced una cosa, coged cualquier cacharro (ajeno) que disponga de una pantalla de Plasma, un relog, un juego electrónico, un monitor plano... y poner vuestro dedazo en toda la pantalla y apretar, veréis (además del guantazo del dueño del artefacto) un efecto similar al mostrado.

Unas últimas notas: Como habréis visto la generación del plasma consume bastante tiempo ya que, en nuestro caso, se requieren más de 64000 tratamientos (de unas 7 u 8 operaciones) para calcular todos los puntos. Como la distribución de puntos es similar y el efecto plasma es prácticamente el mismo, independientemente del fondo generado, una solución bastante extendida es, generar la imagen plasma una única vez para almacenarla en un fichero, de donde será recuperada cada vez que queramos realizar el efecto.

Otro hecho a destacar de los plasma, es su versatilidad. Realizando pequeñas modificaciones sobre el programa anterior, los resultados pueden ser espectaculares. Por ejemplo, podríamos variar la cte de la formula de generación, para darle mayor "rugosidad" o "suavidad" al plasma, también podríamos realizar el plasma para otro colorido en la paleta, rotar los colores en otro orden o dirección o incluso modificar la formula de generación para conseguir otras variedades de efecto. Todas estas modificaciones son sencillas de realizar y con ellas podríais conseguir alguno de estos efectos:

        - Agua fractal.     - Paleta azulada y cte "suave".
        - Fuego fractal.    - Paleta fuego y cte "media".
        - Tormenta fractal. - Paleta grisácea y cte "rugosa".

Muchos de estos efectos los podéis observar en el programa Tom's plasma. El tal (Tom) un químico e informático aficionado, cuenta que él, comenzó su programa con un plasma como el mostrado y que realizando modificaciones, empezó a ampliar su librería de plasmas, hasta alcanzar lo que tiene hoy en día y creedme "es pucho". ;-)

Bueno, nosotros no vamos a realizar nada complejo pero para daros un ejemplo de "la potencia de los plasmas", os vamos a presentar el siguiente programa que no es más que una pequeña modificación del anterior.

Pulsa F2 para ejecutar el programa Fx-Plasma (Demo).    - Posibilidad no disponible en la versión HTML-

Con esto damos por finalizada la explicación, en la próxima lección trataremos nuevos efectos especiales que seguro os interesaran. Asegurate la recepción de la lección subscribiendote al CPV y a la vez nos estarás dando ánimos para continuar. ;-)
 
 


5.11.- Despedida y direcciones.

Nos Vemos

Empiezo recoordandoos (leshe, cuantas 'os' tiene esta palabra) que para cualquier consulta o duda que tengáis respecto al CPV o su distribución podéis pasaros por el area local del CPV en Sakery Fox, (91) - 413 98 55.

En ese area estaremos los autores y bastantes colaboradores, os enteraréis de las últimas noticias, obtendréis posibles tareas para hacer, escucharemos vuestras sugerencias, nos contaremos chistes, quedaremos para tomar unas cervezas o unas sidras y... en fin ... cosas de esas. ;-)

Allí también podrás dejar la siguiente encuesta:

        1. ¿Cómo te enteraste de la existencia del CPV?
             A - A través de un amigo
             B - Vi un mensaje en una BBS
             C - Lo vi anunciado en el teletexto de TVE
             D - Me entere a través de las News de Internet
             E - Un chico alto y moreno me dio un diskette con las lecciones
                 me dijo que lo distribuyera y yo me quede con el diskette.

        2. Si NO eres tu uno de los 50 afortunados de la letra E anterior
           ¿Cómo has conseguido la lecciones del CPV?

        3. Sobre la nueva forma de distribución ...
           ¿Que te parece?
           ¿Cómo la llamarías ;-)?

        4. El precio de 500 pesetas con el que se puede conseguir cada lección
           por correo ¿Lo consideras acertado?

        5. De lo visto hasta el momento en las lecciones...
           ¿Qué es lo que te gusta más?

        6. ¿Qué es lo menos bueno?

        8. Sobre las futuras lecciones del CPV....
           ¿Que tema de los expuestos al comienzo esperas con más impaciencia?

        9. ¿Que temas añadirías o cuales te gustaría ver ampliados?

       10. Observaciones generales sobre el CPV

Una vez expuesta la encuesta de rigor os enumero los distintos sitios donde
podéis conseguir las lecciones que os falten del CPV de libre distribución:

En Fidonet:

-----------------------------------------------------------------------------
 Nombre            | Teléfono         |   Nodo    | Sysop            |Horario
-----------------------------------------------------------------------------
 Sakery Fox (H.Q)  | (91) - 413 98 55 | 2:341/5   | Alberto Ruiz     | 00->24
 Deckard           | (91) - 643 10 67 | 2:341/52  | Pedro de Paz     |   *
 Cesin             | (91) - 532 22 05 | ********* | Francisco Orte   |   *
 La Escuela        | (91) - 501 46 38 | ********* | Victor Vicente   | 00->24
 Pegasus           | (91) - 618 02 25 | ********* | Julio (Aquiles)  | 00->24
 Micaco            | (93) - 398 68 24 | 2:343/156 | David Casas      | 00->24
 Billie Jean       | (95) - 265 53 38 | ********* | **************** |   *
 Psicosis          | (96) - 515 53 13 | 2:346/12  | David Lopez      | 22->13
 Silicon           | (98) - 527 54 53 | ********* | Silicon          | 00->24
 Cisem             | (923) - 12 15 54 | 2:341/59  | Fco.Jose Gonzalez|   *
 Charly            | (948) - 17 59 73 | 2:344/8   | Carlos Segura    | 00->24
 Colon 93          | (959) - 31 92 87 | 2:345/504 | Francisco Gomez  |   *
 Ibosim            | (971) - 34 60 20 | 2:347/12  | Serafín Alarcón  | 00->24
 Flashport         | (972) - 16 25 28 | ********* | **************** | 20->06
-----------------------------------------------------------------------------

y en Internet:

Servidores de FTP

 --- Facultad de Informática          (Universidad Politécnica de Madrid) ---

     asterix.fi.upm.es  en el directorio pub/facultad/alumnos/juegos

 --- Universidad de Valladolid                                            ---

     luna.gui.uva.es    en el directorio pub/magazines/cpv

 --- Escuela Universitaria Politécnica (Universidad de Alcala de Henares) ---

               dulcinea.alcala.es  **** Dirección a confirmar ****

La próxima lección sera la puntilla a esta pequeña gran historia y para ella estamos desarrollando bonitas sorpresas. Puede que no sea el último número pero esto depende de la aceptación que tengan las seis lecciones publicadas. Como siempre nos reservamos la fecha de lanzamiento de la siguiente lección ya que depende de muchos factores, de cualquier forma que sepáis que estamos en ello y que os mantendremos informados, en los sitios de siempre.

En la realización de esta lección han participado:

                       Jose Miguel Espadero
                        Alfredo Huerta
                        Benjamín Moreno Palacios (BMP)
                        Jose Maria Peña Sánchez
                        Francisco Priego
                        Jesús Angel Sánchez Mena (JASM)

A parte de los sufridos redactores queremos dar las gracias a:
 


[ Anterior | Índice | Siguiente ]

La última versión de este texto se podrá encontrar en Internet, en la dirección:
www.nachocabanes.com