 ----------------------------------------------------------------------------
                        
                          TUTORIAL DE PROGRAMACION
                         DE  GRAFICOS  EN  PASCAL
                    
                                
                               
                             
                         
                           
                          
                                 
                                     
                               
                                
                                   
                                     
                     Nmero 10:  - Texto en el modo 13h -
 ----------------------------------------------------------------------------

 ACERCA DE:

        Los tutoriales de programacin, as como los ejemplos que
        se presentan en ellos, son escritos por Alfonso Alba Cadena
        (FAC) y todos ellos pueden ser encontrados (por ahora) en
        el FTP de la Facultad de Ciencias de la UASLP:

           ftp://galia.fc.uaslp.mx/pub/tutorial

        Y en el WWW, en la pgina de programacin de FAC:

           http://galia.fc.uaslp.mx/~ganzo/prog/tutorial.html

        en donde encontrarn adems diversos documentos de programacin
        y algunos demos realizados por FAC, y prximamente por Delabu Alama.


        Para correcciones, dudas, comentarios y sugerencias, dirjanse
        con FAC va e-mail usando alguna de las siguientes direcciones:

             fac@slp1.telmex.net.mx      (preferida)

             shadowfac@hotmail.com       (hotmail (WWW))

             ganzo@galia.fc.uaslp.mx     (Facultad de Ciencias)

        Si desean "suscribirse" a los tutoriales para que les lleguen
        por e-mail cuando salgan, envenme un e-mail con el ttulo
        "Tut Mailing List", adems de su nombre y direccin de e-mail.



 INTRODUCCION:

      Bienvenidos al (hasta ahora) ltimo tutorial de la serie FAC,
      el cual increblemente es el nmero DIEZ!

      Como pueden ver, la presentacin de los tutoriales ha cambiado
      un poco, y tambin el contenido. Debido al escaso tiempo disponible,
      ahora tratar de escribir menos rollo y concentrarme en explicar
      las cosas ms importantes. Haba pensado en presentar los tutoriales
      como una revista electrnica, es decir que ustedes ejecutan un
      programa desde el cual pueden navegar entre los artculos, anuncios,
      cdigo fuente, etc..., sin embargo, esto requiere ms trabajo y no
      tengo tiempo de hacerlo sin ayuda (pero si alguno de ustedes se
      dignara a echarme la mano...).

      Por otra parte, desde ahora los tutoriales incluirn los programas
      de ejemplo ya compilados, para que desde el principio puedan ver
      mas o menos de que se trata.

      Bueno, continuando con el tutorial, esta vez se trata de cmo
      desplegar texto en el modo grfico.

      Todo empez cuando algunos cuates de aqu de Ciencias (hola Edgar!)
      necesitaban hacer un programa que desplegara imgenes PCX junto con
      algunos letreros. El problema es que no es posible cargar un PCX
      con las libreras BGI de turbo pascal y por otro lado, no tenemos
      ninguna instruccin como OutTextXY en las libreras del modo 13h.

      En ese momento, se me ocurrieron tres soluciones:

         1.- La forma ms fcil es dibujar el texto sobre la imagen y
             guardarla en un archivo PCX. Despus, cuando se carga el PCX,
             se observa tanto la imagen como el texto. Sin embargo, esta
             solucin es muy poco prctica, ya que habra que generar un
             PCX para cada combinacin de texto/imagen posible, adems de
             que el texto quedara completamente esttico. En otras palabras,
             esta solucin no resuelve el problema, solamente lo retrasa.

         2.- Otra forma de hacerlo sera escribiendo una rutina para
             cargar PCX usando las libreras BGI, sin embargo, estas
             libreras no disponen de un modo de 320*200 con 256 colores.
             Se dispone de 256 colores pero en altas resoluciones, como
             640*480, 800*600, etc... y con un driver especial (BGI256.BGI).
             Adems de que el BGI es EXTREMADAMENTE LENTO.

         3.- La tercera solucin consiste en implementar procedimientos
             para desplegar texto en el modo 13h. Esta es, en mi opinin,
             la mejor solucin para obtener una buena velocidad y algo
             de versatilidad para usar texto en el modo grfico, aunque
             solamente podemos trabajar en baja resolucin.



CONTENIDO:

          Junto con este tutorial se incluye la unidad CHARSET.PAS,
          la cual dispone de varias rutinas para desplegar texto en
          el modo 13h. Las unidades CHARSET.PAS y MODE13.PAS son
          material FREEWARE, lo cual significa que:

               - No puedes vender las unidades CHARSET.PAS ni MODE13.PAS
               - No puedes vender NINGUN programa que haga uso de esas
                 unidades
               - No puedes colocar CHARSET.PAS ni MODE13.PAS en ningn
                 CD-Rom o diskette comercial sin permiso escrito del autor.
               - Puedes usar las unidades en tus propios programas FREEWARE,
                 SIEMPRE Y CUANDO me des crdito por ello.

          Van a decir que quema mucho el sol, pero la verdad es que hay
          mucha gente a la que le gusta obtener algo a partir del trabajo
          de OTRAS personas (y algunos de ellos estn en Ciencias :) ).

          La unidad CHARSET.PAS empez como un pequeo proyecto para
          resolver el problema de Edgar. Al poco tiempo, me di cuenta
          de que era muy til y decid usarla en mi siguiente demo
          ("China" - nunca terminado), pero la unidad funciona bien y
          creo que se puede expandir an ms.

          La unidad est escrita 100% en Pascal y es suficientemente rpida
          para la mayora de los usos. An as, podra usarse ensamblador
          en algunas partes crticas y aumentar an ms la velocidad.
          El cdigo fuente es relativamente pequeo: alrededor de 150 lneas.

          Ahora la parte mala: La unidad usa un poco de programacin
          orientada a objetos, adems de apuntadores y listas.

          Desgraciadamente, no tengo tiempo para explicar el manejo de
          apuntadores en Pascal ni las listas, pero en cualquier libro
          decente de estructuras de datos deben de venir estos conceptos.

          Y acerca de la POO (programacin orientada a objetos), les
          recomiendo (otra vez) que busquen algn libro que explique los
          conceptos bsicos, aunque este tutorial no hace uso de muchas
          de las ventajas de la POO, es algo ya prcticamente bsico para
          cualquier programador. Por ahora ser suficiente saber qu es
          una clase, un objeto, un constructor, un destructor y un mtodo.

          An as, no debe de ser difcil seguir el cdigo de la unidad,
          y si alguien necesita saber los conceptos bsicos sobre POO y
          no puede conseguir un libro, pueden mandarme un e-mail a alguna
          de las direcciones que se listan al principio.



Ahora s: Texto en el Modo 13h:
-------------------------------

     En realidad, ustedes ya deberan de ser capaces de desplegar texto
     en el modo grfico. Si ya lo han hecho, entonces felicidades!, he
     aqu otra forma de hacerlo. Y si no lo han hecho es porque no se les
     ha ocurrido cmo y les da flojera pensar..... (a quin no?)

     Para escribir texto, lo nico que tenemos que hacer es conseguir un
     montn de pequeas imgenes con formas de letras, lo cual se conoce
     como juego de caracteres, y entonces dibujarlas en la pantalla como
     si fueran sprites.

     Y de dnde se obtiene un juego de caracteres? Bueno, ustedes pueden
     dibujarlo, letra por letra. O pueden fusilarse alguno, o pueden usar
     una fuente TrueType de Windows para dibujar las letras.

     Y cmo se almacena un juego de caracteres? Bueno, la respuesta es
     COMO USTEDES QUIERAN. O mejor dicho: COMO USTEDES PUEDAN.

     En el caso de mi unidad, el juego de caracteres se almacena en una
     imagen PCX de 320*200 a 256 colores. De esta forma, podemos cargar
     el juego de caracteres completo a una pantalla virtual.

     Despus, lo nico que hay que hacer es copiar a la pantalla la regin
     correspondiente al caracter que queremos imprimir. Por ejemplo, si
     la letra 'F' est en la regin rectngular (80, 120) - (100, 140),
     entonces copiamos esa regin a la pantalla, pero en otra posicin.
     Adems, la copia debe ser transparente, es decir que solamente debemos
     dibujar el caracter y no todo el rectngulo que lo contiene. Esto se
     hace exactamente de la misma forma que con los sprites. (Se acuerdan
     de los tostadores voladores?).

     Obviamente, necesitamos conocer en qu regin rectangular se encuentra
     cada caracter, pero esto se simplifica mucho si aplicamos la restriccin
     de que TODOS los caracteres sean del mismo tamao para cada juego.

     Supongamos que hacemos un juego de caracteres de 32 por 32 pxels
     cada caracter, y que la letra 'A' se localiza en el rectngulo
     (0, 0) - (31, 31). La letra 'B' en (32, 0) - (63, 31), etc...
     Entonces, el cdigo para dibujar una letra en la pantalla es
     mas o menos como el siguiente:


         for y := 0 to 31 do
             for x := 0 to 31 do
             begin
                  c := GetPixel(ix + x, iy + y, CharSetSeg);
                  PutPixel(px + x, py + y, c, VGA);
             end;

         donde:  x, y:  son variables utilizadas para los ciclos.

                 c:  es una variable temporal (tipo byte).

                 CharSetSeg:  es el segmento de la pantalla virtual donde
                              se encuentra el juego de caracteres.

                 (px, py):  son las coordenadas en donde se despliega el
                            texto. El texto est alineado abajo y a la
                            derecha de este punto.

                 (ix, iy):  son las coordenadas del punto superior izquierdo
                            del rectngulo que contiene al caracter que
                            queremos imprimir (en la pantalla virtual).


     no, no, no, no, no.... dijimos que el copiado debera de ser
     transparente, por lo tanto, el cdigo correcto es este:

         for y := 0 to 31 do
             for x := 0 to 31 do
             begin
                  c := GetPixel(ix + x, iy + y, CharSetSeg);
                  if c <> 0 then PutPixel(px + x, py + y, c, VGA);
             end;


     La unidad CHARSET.PAS siempre usa el color 0 como color transparente,
     debido a que a la hora de optimizar el cdigo con ensamblador, la
     comparacin con cero se hace mas fcil y mas rpido, pero el cdigo
     en Pascal puede ser modificado para comparar con cualquier color
     (o incluso con un rango de colores) y hacerlos transparentes sin
     perder velocidad.

     Pero entonces, qu se necesita para generar un juego de caracteres?

     Bueno, hasta ahora se necesita la siguiente informacin:

            - El tamao de los caracteres

            - Una imagen del juego de caracteres en la cual cada caracter
              ocupa una regin rectangular del tamao indicado. Esta imagen
              se carga a una pantalla virtual. A esta imagen yo le llamo
              "mapa de caracteres".

            - La posicin de cada caracter en el mapa. Es decir, las
              coordenadas de la esquina superior izquierda de cada
              caracter. Estas coordenadas se deben almacenar en una tabla.


     En un programa de dibujo tpico como Paint Shop Pro o Photoshop,
     es un poco difcil dibujar un juego completo de caracteres, an
     utilizando las fuentes TrueType de Windows. No se preocupen,
     includos con este tutorial vienen tres juegos de caracteres y
     al final vienen varios tips para crear los suyos propios.


Posiciones de los caracteres en el mapa:
----------------------------------------

     Ya dijimos que necesitamos guardar las posiciones iniciales de
     cada caracter en un arreglo.... pero de qu forma?

     Bueno, lo mejor es guardarlos en el mismo orden en que la computadora
     ordena los caracteres, es decir, en orden ASCII. Para ello, vamos a
     crear una pequea estructura que almacene la posicin de cada caracter:


               type TCharInfo = record
                                x, y : word;
                                end;


     Es muy sencilla, pero es suficiente, puesto que eso es todo lo que
     necesitamos saber acerca de cada caracter. Ahora generamos un
     arreglo de tipo TCharInfo para almacenar la informacin de TODOS
     los caracteres. El nmero mximo de caracteres que un juego puede
     tener es 256, puesto que el cdigo ASCII es de un byte de longitud,
     as que vamos a crear un arreglo de 256 elementos TCharInfo:

             var Chars : array[char] of TCharInfo;

     Un momento... qu es eso de array[char] ???

     Bueno, al declarar un array de esa forma, en realidad estamos declarando
     un arreglo cuyos ndices ocupan todo el rango del tipo char, es decir
     desde el caracter cuyo ASCII es cero, hasta el que tiene ASCII 255.

     Por lo tanto, nosotros podemos acceder al array de la siguiente forma:

         x := Chars['A'].x;  { coordenada X del caracter 'A'                }
         y := Chars[#32].y;  { coordenada Y del caracter ASCII 32 (espacio) }

     Eso facilita mucho las cosas, no? Pascal es muy bueno cuando de
     arreglos se trata.... bueno, arreglos pequeos :)

     Ahora, el arreglo Chars contiene la informacin de la posicin de cada
     caracter, pero en realidad esa es solamente una parte de la informacin
     del JUEGO de caracteres, por lo tanto, vamos a crear una estructura
     que incluya al arreglo Chars y al resto de la informacin necesaria
     para nuestro juego de caracteres:


                  type TCharSetInfo = record
                                      Chars : array[char] of TCharInfo;
                                      SizeX, SizeY : byte;
                                      charmap : string[12];
                                      end;


     Los campos del tipo TCharSetInfo son los siguientes:

         Chars : contiene la informacin de la posicin de cada caracter

         SizeX, SizeY : dimensiones X y Y de TODOS los caracteres (tamao)

         charmap : nombre del archivo PCX en donde se almacena la imagen
                   del juego de caracteres


     Como pueden ver, el tipo TCharSetInfo contiene toda la informacin
     necesaria para trabajar con un juego de caracteres. Lo dems es
     simplemente generar una pantalla virtual y cargar el archivo PCX
     en ella para tomar los caracteres de ah.

     Adems, puesto que toda la informacin est contenida en un registro,
     es muy fcil guardarla en un archivo una vez que se ha generado la
     informacin y despus simplemente cargar ese archivo.

     Bueno, pero todava no terminamos. Nos falta implementar funciones
     para cargar un juego de caracteres e imprimir letras y cadenas.

     Tomen un descanso y asegrense de que han entendido todo hasta aqu,
     despus regresen para conocer a la Clase Estelar de la Noche....


La clase TCharSet:
------------------

     Una clase es como una plantilla a partir de la cual se crean los
     "objetos". Analgicamente, podramos decir que una clase es como
     un tipo de variable y un objeto es una variable de ese tipo.

     De hecho, las clases son muy parecidas a los registros, ya que
     stas pueden contener campos (variables) de cualquier otro tipo,
     pero adems, las clases contienen funciones y procedimientos
     especiales para trabajar con los campos de datos. A estas funciones
     y procedimientos se les llama METODOS.

     Por lo tanto, un OBJETO es una estructura que contiene distintos
     campos de datos y mtodos para trabajar con esos datos. La forma
     de acceder a los campos y mtodos de un objeto es exactamente
     igual que como se accede a los campos de un registro. Sin embargo,
     algunos campos y/o mtodos pueden ser declarados como PRIVADOS
     (private) de forma que solamente se puede acceder a ellos desde
     los mtodos de esa misma clase... (bueno, en TurboPascal no es
     as, exactamente :) )

     Adems, una clase puede (debe) tener dos mtodos especiales, llamados
     CONSTRUCTOR y DESTRUCTOR. El primero sirve para realizar toda la
     inicializacin que la clase necesite, especialmente para reservar
     memoria y cosas as. El destructor sirve para hacer lo contrario,
     es decir, liberar la memoria que el objeto haya reservado.

     ***NOTA*** En lenguajes como Visual Basic o Delphi, a los campos
                de datos de un objeto se les llama "propiedades".

     Bueno, eso fue una explicacin mas o menos chafa de la POO, pero
     puede ayudar a alguno que otro despistado por ahi...


     La clase TCharSet contiene toda la informacin necesaria sobre
     un juego de caracteres, incluyendo la pantalla virtual en la que
     est almacenado el juego y la paleta de colores del archivo PCX.
     Tambin incluye los mtodos necesarios para inicializar y destrur
     los objetos, cargar un juego de caracteres desde el disco y desplegar
     una cadena (y una lista de cadenas) en la pantalla.

     He aqu la definicin de la clase TCharSet (y su apuntador PTCharSet):


        type PTCharSet = ^TCharSet;
             TCharSet = object
                        public
                              Info : TCharSetInfo;
                              MapScr : PTVirtual;
                              MapSeg : word;
                              AllText : PTTextInfo;
                              Palette : TPalette;

                              constructor Init(fn : string);
                              destructor Done;

                              procedure Load(fn : string);

                              procedure PrintChar(x, y : word; c : char;
                                                  where : word);

                              procedure PrintString(x, y : word; s : string;
                                                 hs : integer; where : word);

                              procedure AddText(x, y : word; s : string;
                                                hs : integer);

                              procedure ClearText;

                              procedure PrintAll(where : word);
                        end;


     Vamos a ver qu es cada cosa. Por ahora vamos a olvidarnos del campo
     AllText y de los mtodos AddText, ClearText y PrintAll. Esos los
     veremos despues.


Campos de datos de TCharSet:

       Info : TCharSetInfo  -- Esta estructura la analizamos hace rato
                               y contiene toda la informacin necesaria
                               para el juego de caracteres, excepto por
                               el mapa de caracteres.

       MapScr : PTVirtual   -- Apuntador a la pantalla virtual en donde
                               se guarda el mapa de caracteres.

       MapSeg : word        -- Segmento de la pantalla virtual en donde
                               se guarda el mapa de caracteres.

       Palette : TPalette   -- Paleta extrada del archivo PCX en donde
                               se guarda el mapa de caracteres.


Mtodos de la clase TCharSet:

       constructor Init(fn : string);

       -- Este es el constructor de la clase. Recibe un solo parmetro, el
          cual es el nombre del archivo en donde se encuentra la informacin
          del juego de caracteres. Este archivo NO ES EL ARCHIVO PCX que
          contiene la imagen del juego, sino un archivo que contiene una
          estructura de tipo TCharSetInfo. Luego veremos cmo crear esos
          archivos.

          La implementacin del constructor es muy sencilla:

             constructor TCharSet.Init(fn : string);
             begin
                  SetupVirtual(MapScr, MapSeg);
                  if fn <> '' then Load(fn);
                  AllText := nil;
             end;

          Primero se reserva la memoria para la pantalla virtual que contiene
          el mapa de caracteres. Luego carga la informacin a partir del
          archivo utilizando el mtodo Load, a menos de que se le pase
          como parmetro una cadena vaca, con lo cual no carga ninguna
          informacin. Esa es toda la inicializacin que se necesita.


       destructor Done;

       -- El destructor tiene tambin una implementacin muy sencilla:

             destructor TCharSet.Done;
             begin
                  if MapScr <> nil then ShutDownVirtual(MapScr);
                  if AllText <> nil then dispose(AllText, Done);
                  MapScr := nil;
                  AllText := nil;
             end;

          Lo nico que hace es liberar cualquier memoria que se haya
          reservado. En este caso solamente se reserva memoria para
          el mapa de caracteres y para una lista de cadenas que veremos
          despus. Siempre es una buena idea hacer los apuntadores
          igual a nil despus de liberar la memoria que apuntaban.


       procedure Load(fn : string);

       -- Este procedimiento carga un juego nuevo de caracteres a partir
          de un archivo con la informacin necesaria. Como ya vimos, este
          procedimiento puede ser llamado desde el constructor.

             procedure TCharSet.Load(fn : string);
             var f : file of TCharSetInfo;
             begin
                  assign(f, fn);
                  reset(f);
                  read(f, Info);
                  LoadPCX(Info.CharMap, MapSeg, 320, 200, 0, 0, Palette);
                  close(f);
             end;

          No creo que haya ningn problema para entenderlo. Primero se
          carga la informacin en el campo Info y luego se carga el
          archivo PCX que contiene el mapa de caracteres.


       procedure PrintChar(x, y : word; c : char; where : word);

       -- Este es el procedimiento que imprime un solo caracter en la
          pantalla. Es prcticamente el mismo que vimos al principio,
          pero en lugar de utilizar PutPixel, se accede directamente
          a la memoria de la pantalla. De esta forma es mucho mas fcil
          traducirlo a ensamblador. Adems se deja un espacio marginal
          de un pixel, por lo que un caracter de 32 * 32, en realidad
          se imprime como de 31 * 31.

          Los parmetros que toma son los siguientes:

              (x, y) - posicin donde se va a imprimir el caracter
                c    - caracter a imprimir
              where  - segmento de la pantalla en donde se va a imprimir

          El procedimiento lo pueden ver en el cdigo de CHARSET.PAS.


       procedure PrintString(x, y : word; s : string; hs : integer;
                             where : word);

       -- Este mtodo imprime una cadena completa. Los parmetros son
          los siguientes:

              (x, y) - posicin en donde se va a imprimir la cadena
                s    - cadena a imprimir
                hs   - separacin entre caracteres
              where  - segmento de la pantalla donde se va a imprimir

          El procedimiento solamente recorre la cadena e imprime un caracter
          despus (a la derecha) de otro. Adems se incluye un parmetro para
          controlar la separacin entre caracteres, la cual incluso puede
          ser negativa en el caso de que las letras estn muy separadas.

          La implementacin es casi trivial:

             procedure TCharSet.PrintString(x, y : word; s : string;
                                            hs : integer; where : word);
             var i, xx : word;
             begin
                  xx := x;
                  for i := 1 to length(s) do
                  begin
                       PrintChar(xx, y, s[i], where);
                       inc(xx, Info.SizeX);
                       inc(xx, hs);
                  end;
             end;

          Hay que tener en cuenta que NO SE HACE ninguna comprobacin de
          lmites (clipping) y por lo tanto, una cadena muy larga puede
          pasarse del lado derecho de la pantalla al lado izquierdo.


       Estos son los mtodos bsicos para imprimir texto en modo 13h.
       Echenle un ojo al programa de ejemplo TXTDEMO1.PAS el cual
       utiliza todos los mtodos anteriores. Vern que el cdigo se
       vuelve muy fcil de entender cuando se usan objetos.

       Recuerden que los ejemplos ya vienen compilados, as que ya
       pueden ejecutar directamente el programa TXTDEMO1.EXE

       Despus de que hayan analizado el ejemplo y entendido lo anterior,
       continen con la siguiente parte...



Cmo generar un juego de caracteres:
------------------------------------

     Bueno, supongo que si vieron el programa TXTDEMO1, se habrn dado
     cuenta de que los tres juegos de caracteres que se incluyen con este
     tutorial no son muy buenos. Esto es porque con una resolucin de
     320 * 200, no se puede obtener una gran calidad de imagen. Afortunada-
     mente, disponemos de suficientes colores para usar antialiasing en las
     letras, pero por otra parte, yo us unos tipos de letra de Windows para
     hacer los juegos (excepto las letras chinas) y no me preocup mucho
     por la calidad de stos.

     Esto significa que ustedes querrn hacer sus propios juegos de
     caracteres, tal vez ms grandes o ms chicos, o tal vez utilizando
     varios colores, ya que las rutinas lo permiten.

     Los pasos a seguir para hacer un juego de caracteres nuevo son los
     siguientes:

          a) Usar un programa de dibujo lo suficientemente DECENTE como
             para poder abrir y salvar en formatos BMP y PCX. Ayuda mucho
             el que se puedan dibujar letras en el programa utilizado. :)

             Pueden usar incluso el Paintbrush, aunque si quieren "deformar"
             o darle algn efecto a las letras, necesitarn algo ms
             potente.

             Yo personalmente les recomiendo el Paint Shop Pro 4.12 y el
             Adobe Photoshop 4.0, que son los que yo uso.

          b) Dentro de ese programa, abrir uno de los siguientes archivos:

                    SET16X16.BMP     SET20X20.BMP     SET32X32.BMP

             Estas imgenes solamente tienen dibujado un cuadriculado
             de 16 * 16, 20 * 20 y 32 * 32, respectivamente. Ustedes
             pueden basarse en este cuadriculado para dibujar su juego
             de caracteres, aunque si desean usar otro tamao, tendrn
             que hacer su propio cuadriculado.

             A propsito, si el programa les permite abrir estos archivos
             en modo de slo lectura, hganlo. Cranme que no es buena idea
             sobreescribir alguna de stas imgenes, ya que se pueden
             reciclar las veces que quieran.

          c) Procedan a dibujar las letras y smbolos de su juego de
             caracteres. Esta es la parte ms tediosa de todas.

             Obviamente, pueden hacer lo mismo que yo y utilizar una
             fuente de Windows para dibujar las letras. Otra forma
             es dibujarlas a mano. Y otra forma es copiar y pegar las
             letras a partir de otra imagen. Existen libros que contienen
             juegos completos de letras y que se pueden escanear,
             de hecho, as fu como obtuvimos las letras chinas.

             Ah, y por cierto, no se salgan de la "raya". Es decir que
             cada "casilla" del cuadriculado corresponde a un caracter
             y no es posible (con estas rutinas) obtener caracteres ms
             grandes dentro del mismo juego.

             Otra cosa que hay que tener MUY EN CUENTA es el orden en que
             se dibujan los caracteres. Aunque pueden usar el orden que
             ustedes quieran, yo les recomiendo que se apeguen al ASCII
             lo ms que puedan. Es decir, dibujen la B despus de la A
             y antes de la C. Y letras como la , djenlas hasta el final
             para evitar que rompan el orden ASCII del juego.

             Esto se hace para que a la hora de generar la informacin
             con la posicin de cada caracter, podamos usar un ciclo
             que calcule las posiciones, en lugar de escribirlas una
             por una.

             Vean los archivos CHARSET?.PCX para ejemplos de juegos de
             caracteres y el orden que pueden tener. En este caso, el
             juego CHARSET3.PCX es el mejor ordenado ya que se apega
             100% al orden ASCII, desde el ASCII #32 (espacio) hasta el
             ASCII #125 (llave derecha - } ).

          d) Una vez que el juego este dibujado hay que guardar la imagen
             como PCX de 256 colores (8 bit). Desgraciadamente, muchos
             programas no nos permiten manipular la paleta de colores,
             as que es posible que se tenga que usar otro programa extra
             para reducir los colores y acomodar la paleta. Otra vez, les
             recomiendo el Paint Shop Pro v4.12.

          e) Ahora sigue una parte interesante: La creacin del archivo
             de informacin (*.CHR).

             Lo nico que hay que hacer es un pequeo programita para
             crear una estructura de tipo TCharSetInfo con la informacin
             del juego de caracteres y luego guardar esa estructura en
             un archivo. Fcil, eh?

             Bueno, solo para que lo recuerden, la estructura TCharSetInfo
             est definida de esta manera:

                  type TCharSetInfo = record
                                      Chars : array[char] of TCharInfo;
                                      SizeX, SizeY : byte;
                                      charmap : string[12];
                                      end;

             Entonces, nuestro programita, al que llamaremos "generador"
             de la informacin queda como sigue:

                program GenInfo;

                uses CharSet;           { necesitamos inclur la unidad }

                var Info : TCharSetInfo;
                    f : file of TCharSetInfo;
                    x, y : word;        { despus vamos a usar estas }
                    c : char;           { contador para los ciclos   }

                begin
                     fillchar(Info, sizeof(Info), 0);
                     Info.CharMap := 'mijuego.pcx';
                     Info.SizeX := 20;
                     Info.SizeY := 20;
                     ...

                Hasta ahora lo que hace el programa es primero limpiar
                la estructura Info, llenndola con ceros. Despus establece
                el NOMBRE DEL ARCHIVO PCX en donde ustedes almacenaron
                la imagen del juego de caracteres. Y tambin se establece
                el TAMAO de cada caracter. En este caso son de 20 * 20.

                Lo que sigue es generar la informacin de la posicin de
                cada caracter. Para ejemplificar esto, digamos que el
                mapa de caracteres est mas o menos as:

                      0     20    40    60    80   100   120
                    0 -------------------------------------------
                      |     |     |     |     |     |     |     |
                      |  A  |  B  |  C  |  D  |  E  |  F  |  G  |
                      |     |     |     |     |     |     |     |
                   20 -------------------------------------------
                      |     |     |     |     |     |     |     |
                      |  H  |  I  |  J  |  K  |  L  |  M  |  N  |
                      |     |     |     |     |     |     |     |
                   40 -------------------------------------------
                      |     |     |     |     |     |     |     |
                      |  O  |  P  |  Q  |  R  |  S  |  T  |  U  |
                      |     |     |     |     |     |     |     |
                   60 -------------------------------------------
                      |     |     |     |     |     |     |     |
                      |  V  |  W  |  X  |  Y  |  Z  |     |  !  |
                      |     |     |     |     |     |     |     |
                      -------------------------------------------

                Es decir que as es como ustedes dibujaron las letras
                en los pasos anteriores. Bueno, como los caracteres son
                de 20 * 20 es muy fcil calcular la posicin de cada letra,
                por ejemplo:

                    la 'A' est en (0, 0)
                    la 'B' est en (20, 0)
                    la 'C' est en (40, 0)
                    la 'J' est en (40, 20)
                    la 'S' est en (80, 40)
                    el '!' est en (120, 60)

                Recuerden que lo que necesitamos saber son las coordenadas
                de la esquina superior izquierda de cada caracter.

                En el caso de que el tamao sea 32 * 32, por ejemplo,
                habra que tomar intervalos de 32, pero es igual de fcil.

                Supongo que con esto se entiende cmo se obtiene la
                posicin de cada caracter. Obviamente tienen que FIJARSE
                en el mapa que dibujaron y en el tamao de los caracteres.

                Bueno, entonces lo que sigue en el programa "generador"
                es algo como lo siguiente:

                   ...       { continuacin del "generador" }
                   Info.Chars['A'].x := 0;
                   Info.Chars['A'].y := 0;
                   Info.Chars['B'].x := 20;
                   Info.Chars['B'].y := 0;
                   Info.Chars['C'].x := 40;
                   Info.Chars['C'].y := 0;
                   ...      { aqu continua con 'D', 'E', 'F', etc... }
                   Info.Chars[' '].x := 100;
                   Info.Chars[' '].y := 60;
                   Info.Chars['!'].x := 120;
                   Info.Chars['!'].y := 60;
                   ...

                Es muy fcil, no? Y tambin MUY tedioso. Pero si nos
                fijamos un poco, encontramos que hay una secuencia.
                Los caracteres de la 'A' a la 'Z' estn en orden ASCII
                y sus posiciones varan regularmente, por lo tanto
                podemos usar un ciclo para calcular todas sus posiciones.

                La parte anterior del programa se puede sustitur por
                algo como esto:

                     ...
                     for c := 'A' to 'Z' do
                     begin
                          x := ((ord(c) - ord('A')) mod 7) * 20;
                          y := ((ord(c) - ord('A')) div 7) * 20;
                          Info.Chars[c].x := x;
                          Info.Chars[c].y := y;
                     end;
                     { los siguientes caracteres estn fuera de orden ASCII }
                     Info.Chars[' '].x := 100;
                     Info.Chars[' '].y := 60;
                     Info.Chars['!'].x := 120;
                     Info.Chars['!'].y := 60;
                     ...


                Como pueden ver, se simplifica mucho el trabajo.

                Si se preguntan que de dnde salio el "mod 7" y el "div 7"
                en las frmulas anteriores, solo fjense en el mapa de
                caracteres que dibuj arriba (tiene SIETE columnas).

                Como pueden ver, conviene tener los caracteres en el orden
                ASCII. Si se fijan en el programa GENINFO3.PAS, solamente
                se usa UN CICLO para generar todo el juego de caracteres,
                debido a que este est perfectamente ordenado.

                Despus de generar las posiciones de todos los caracteres,
                debemos guardar la estructura Info en un archivo. Eso
                es muy fcil de hacer:

                       ...
                       assign(f, 'mijuego.chr');
                       rewrite(f);
                       write(f, Info);
                       close(f);
                       end.

                Yo acustumbro usar el mismo nombre para un archivo de
                informacin que para el archivo PCX correspondiente,
                por ejemplo, si el la imagen PCX se llama 'juego1.pcx',
                entonces, al archivo de informacin lo llamo 'juego1.chr'.

                Ese archivo es el que se utiliza cuando cargamos un juego
                de caracteres a la memoria, de esta forma:

                   CharSet := new(PTCharSet, Init('mijuego.chr'));

                Y esos son todos los pasos que hay que seguir para crear
                un juego de caracteres. Obviamente, es algo muy tedioso,
                pero al final se obtienen buenos resultados.

                Recuerden siempre tener MUY en cuenta la paleta de colores.
                Siempre hay que recordar qu colores son usados por el
                juego de caracteres, por el fondo, por los sprites, etc...,
                de forma que no tengamos problema al crear la paleta que
                vamos a usar.

                Revisen los archivos generadores (GENINFO?.PAS) para los tres
                juegos de caracteres que se incluyen. Algunos rompen un poco
                el orden ASCII y necesitan usar dos o tres ciclos para
                generar la informacin. Y tambin les recomiendo que hagan
                un juego de caracteres por su cuenta, aunque sea solamente
                para practicar.


Mtodos adicionales de la clase TCharSet:
-----------------------------------------

     En el ejemplo pudimos ver que la clase TCharSet tiene los mtodos
     necesarios para imprimir una cadena, al menos horizontalmente, y
     para cargar un nuevo juego de caracteres. Esto normalmente es
     suficiente, pero en algunos demos o presentaciones se vuelve muy
     complicado hacer algo como lo siguiente:

     Digamos que ustedes estn haciendo una presentacin en la que la
     imagen de fondo cambia contnuamente (por ejemplo, un plasma). Y
     adems sobre esa imagen aparecen varios letreros en diferentes
     lugares de la pantalla (por ejemplo, los crditos del programa
     o algo as).

     En ASCII se ve mas o menos as:

                    ---------------------------------
                    |                               |
                    |  cdigo:                      |
                    |  fulano                       |
                    |                   grficos:   |
                    |                   mengano     |
                    |      msica:                  |
                    |      sutano                   |
                    |                               |
                    ---------------------------------

     Digamos que la "pantalla" anterior presenta los crditos de su
     programa. Y todo eso se muestra sobre un fondo que NO es esttico,
     digamos, un plasma.

     Entonces, el cdigo que presenta esa pantalla podra ser mas o menos
     como sigue:

                while not keypressed do
                begin
                     DibujaPlasma(VirSeg);      { dibuja plasma en la P.V. }
                     ChSet^.PrintString(10, 10, 'cdigo:', -5, VirSeg);
                     ChSet^.PrintString(10, 20, 'fulano', -5, VirSeg);
                     ChSet^.PrintString(200, 50, 'grficos:', -5, VirSeg);
                     ChSet^.PrintString(200, 60, 'mengano', -5, VirSeg);
                     ChSet^.PrintString(80, 120, 'msica:', -5, VirSeg);
                     ChSet^.PrintString(80, 130, 'sutano', -5, VirSeg);
                     CopyScreen(VirSeg, VGA);
                end;


     Como pueden ver, son muchos PrintString los que hay que usar. Adems,
     sera peor si quisiramos que primero apareciera "cdigo: fulano",
     y despus de cierto tiempo, que apareciera "grficos: mengano" y
     poco despus "msica: sutano". Para hacer eso, tendramos que hacer
     tres o cuatro ciclos como el anterior.

     Es por eso que le agregu un objeto nuevo a la unidad CharSet y tres
     mtodos nuevos a la clase TCharSet. Estos mtodos permiten imprimir
     una serie de cadenas en diferentes posiciones y agregar ms cadenas
     a la serie.

     Esto se hace utilizando una clase nueva: TTextInfo, la cual es
     simplemente un elemento de una lista ligada, que contiene la
     informacin necesaria para imprimir una cadena.

     No es necesario conocer la clase TTextInfo para poder usar los
     mtodos nuevos de TCharSet, as que si solamente quieren usar
     los mtodos, o si no entienden an cmo funciona una lista ligada,
     pueden saltarse esta parte.


La clase TTextInfo:
-------------------

              type PTTextInfo = ^TTextInfo;
                   TTextInfo = object
                               private
                                      txt : string[80];
                                      x, y : word;
                                      hs : integer;
                                      next : PTTextInfo;
                               public
                                     constructor Init(nx, ny : word;
                                                 s : string; nhs : integer);
                                     destructor Done;
                               end;


     Los campos de datos de esta clase son los siguientes:

         txt : string[80]   -- Esta es la cadena que se va a imprimir

         (x, y)             -- Posicin en donde se imprime la cadena

         hs : integer       -- Espacio horizontal entre caracteres

         next : PTTextInfo  -- Apuntador al siguiente elemento de la lista


     Los mtodos de la clase son solamente los siguientes:

         constructor Init(nx, ny : word; s : string; nhs : integer);

              -- Este es el constructor de la clase. Realiza toda la
                 inicializacin necesaria (que no es mucha) y establece
                 el elemento como final de la lista (next := nil).

                 La implementacin es como sigue:

                    constructor TTextInfo.Init(nx, ny : word;
                                               s : string; nhs : integer);
                    begin
                         x := nx;
                         y := ny;
                         txt := s;
                         hs := nhs;
                         next := nil;
                    end;


         destructor Done;

              -- Este es el destructor de la clase, que adems destruye
                 todos los elementos que le siguen en la lista.

                 La implementacin es recursiva y muy sencilla:

                    destructor TTextInfo.Done;
                    begin
                         if next <> nil then dispose(next, Done);
                         next := nil;
                    end;


     Entonces, ya podemos crear una lista cuyos elementos son estructuras
     con la informacin de cada cadena que se va a imprimir. Lo nico que
     falta ahora es implementar los mtodos en TCharSet que hacen uso de
     esta lista.


Mtodos de TCharSet para listas de cadenas:
-------------------------------------------

     La clase TCharSet incluye tres mtodos sencillos para usar la lista
     anterior. Todos estos mtodos utilizan un campo de datos de la clase
     que no hemos mencionado: el campo AllText.

     Este campo es de tipo PTTextInfo y apunta al primer elemento de la
     lista ligada. Mediante AllText, nosotros podemos fcilmente acceder
     a todos los elementos de la lista.

     Los tres nuevos mtodos son los siguientes:

         procedure AddText(x, y : word; s : string; hs : integer);

              -- Este mtodo aade un elemento a la lista de cadenas.
                 El elemento se aade al principio de la lista debido
                 a que es lo ms fcil, pero esto no influye en nada.

                 La implementacin es muy sencilla:

                    procedure TCharSet.AddText(x, y : word; s : string;
                                               hs : integer);
                    var temp : PTTextInfo;
                    begin
                         temp := new(PTTextInfo, Init(x, y, s, hs));
                         temp^.next := AllText;
                         AllText := temp;
                    end;


                 Primero creamos un nuevo elemento y le damos los valores
                 iniciales. Luego lo agregamos al resto de la lista y
                 hacemos que ste sea el primer elemento.


         procedure ClearText;

              -- Este procedimiento borra todos los elementos de la lista,
                 con lo que se puede usar para reiniciar la lista. El
                 procedimiento es como sigue:

                    procedure TCharSet.ClearText;
                    begin
                         if AllText <> nil then dispose(AllText, Done);
                         AllText := nil;
                    end;


                 Primero comprobamos si realmente existe una lista, y si
                 es as, entonces destrumos el primer elemento, con lo
                 cual se destruyen todos los siguientes. Luego nos
                 aseguramos de que AllText sea igual a nil para saber
                 que la lista est vaca.


         procedure PrintAll(where : word);

              -- Este es el procedimiento que imprime todas las cadenas
                 de la lista, usando el mtodo PrintString.

                 Esta es la implementacin:

                      procedure TCharSet.PrintAll(where : word);
                      var temp : PTTextInfo;
                      begin
                           temp := AllText;
                           while temp <> nil do
                           begin
                                PrintString(temp^.x, temp^.y, temp^.txt,
                                            temp^.hs, where);
                                temp := temp^.next;
                           end;
                      end;

                 Simplemente tomamos el primer elemento (AllText) e
                 imprimimos la cadena que contiene. Luego tomamos el
                 siguiente elemento y continuamos con el ciclo hasta
                 llegar al ltimo elemento, en el cual, su campo
                 next es igual a nil.

     Bueno, todo esto requiere un poco de conocimiento de listas y
     apuntadores para entender cmo funciona, pero no es necesario
     saberlo para poder utilizar los procedimientos.

     En realidad, es muy fcil usar estos mtodos. Lo nico que hay que
     hacer, es aadir todas las cadenas que queramos imprimir a la lista,
     y luego usar el mtodo PrintAll para desplegarlas. Si despus queremos
     volver a borrar la lista, usamos el mtodo ClearText.

     Como ejemplo, voy a modificar el ciclo que vimos anteriormente, el
     que dibujaba los crditos de un programa sobre un plasma.

     El cdigo original era este:

                while not keypressed do
                begin
                     DibujaPlasma(VirSeg);      { dibuja plasma en la P.V. }
                     ChSet^.PrintString(10, 10, 'cdigo:', -5, VirSeg);
                     ChSet^.PrintString(10, 20, 'fulano', -5, VirSeg);
                     ChSet^.PrintString(200, 50, 'grficos:', -5, VirSeg);
                     ChSet^.PrintString(200, 60, 'mengano', -5, VirSeg);
                     ChSet^.PrintString(80, 120, 'msica:', -5, VirSeg);
                     ChSet^.PrintString(80, 130, 'sutano', -5, VirSeg);
                     CopyScreen(VirSeg, VGA);
                end;


     Ahora, podemos escribirlo de esta forma:

                ChSet^.ClearText; { nos aseguramos que la lista est vaca }
                ChSet^.AddText(10, 10, 'cdigo:', -5);
                ChSet^.AddText(10, 20, 'fulano', -5);
                ChSet^.AddText(200, 50, 'grficos:', -5);
                ChSet^.AddText(200, 60, 'mengano', -5);
                ChSet^.AddText(80, 120, 'msica:', -5);
                ChSet^.AddText(80, 130, 'sutano', -5);
                while not keypressed do
                begin
                     DibujaPlasma(VirSeg);
                     ChSet^.PrintAll(VirSeg);
                     CopyScreen(VirSeg, VGA);
                end;


     Y a ver qu les parece escribirlo de esta forma:


                type TLetrero = record
                                x, y : word;
                                s : string[80];
                                end;

                const Letr : array[1..8] of TLetrero =
                             ((x:10; y:10; s:'cdigo:'),
                              (x:10; y:20; s:'fulano'),
                              (x:200; y:50; s:'grficos:'),
                              (x:200; y:60; s:'mengano'),
                              (x:80; y:120; s:'msica:'),
                              (x:80; y:130; s:'sutano'));

                ...  { ms declaraciones }

                begin
                     ... { otra parte del programa }

                     ChSet^.ClearText;
                     for i := 1 to 6 do
                         ChSet^.AddText(Let[i].x, Let[i].y, Let[i].s, -5);
                     while not keypressed do
                     begin
                          DibujaPlasma(VirSeg);
                          ChSet^.PrintAll(VirSeg);
                          CopyScreen(VirSeg, VGA);
                     end;
                     ...


     Qu tal, eh? Al principio parece ms trabajo, pero realmente, este
     ltimo ejemplo es muy flexible. Podemos manipular fcilmente las
     cadenas y su posicin, y si queremos, podemos hacer que se impriman
     ms cadenas sin tener que agregar un solo AddText o PrintString,
     solamente las aadimos en el arreglo Letr[].

     Otra cosa que se puede hacer es imprimir las cadenas una por una
     despus de determinado tiempo, pero utilizando solamente un ciclo.
     Si quieren ver un ejemplo de cmo se hace esto, revisen el programa
     TXTDEMO2.PAS, el cual utiliza ampliamente la lista de cadenas.


Conclusin:
-----------

     Bueno, ahora ya saben cmo incluir texto en sus programas. Obviamente,
     la calidad del texto no es muy buena debido a la baja resolucin del
     modo 13h, pero por otra parte, no sera ningn problema portar el
     cdigo a otras resoluciones. Solamente hay que modificar el mtodo
     PrintChar.

     Adems, la unidad CharSet todava se le pueden aadir ms funciones,
     por ejemplo, imprimir cadenas verticalmente, o un mtodo para eliminar
     cadenas de la lista. Es muy fcil eliminar un elemento del principio
     de una lista, y tambin del final, por lo tanto podemos hacer tambin
     que el texto "desaparezca" por partes. Pero eso se los dejo a ustedes.

     Les gust la programacin orientada a objetos? Si quieren, podemos
     organizar nuestras rutinas de 3D en clases y eso facilitara MUCHO
     las cosas. Escrbanme un e-mail o algo...


Qu sigue?
----------

     An quedan muchas cosas por ver, sin embargo, con lo que han aprendido
     hasta ahora, tienen suficiente para empezar a hacer un pequeo demo,
     juego o presentacin. De hecho, los programas de ejemplo ya casi son
     micro-demos :-)

     Recuerden que me gustara ver cualquier cosa que hagan en la que
     utilicen lo de los tutoriales. As que anmense y mndenme sus
     programas por e-mail, aunque sea solamente algo pequeo.

     Bueno, para los siguientes tutoriales tengo planeado algo como esto:

            - Tutorial #11: Iluminacin y sombreado plano

            - Tutorial #12: Ordenacin de polgonos para objetos complicados

            - Tutorial #13: Ms efectos: estelas de movimiento, tneles,
                                         distorsin de bitmaps

     Y eso es solamente para empezar. Adems veremos prximamente:

           - Sombreado Gouraud y Phong
           - Mapeado de textura
           - Mapeado de entorno
           - Rotozoomers
           - Generacin de texturas y entornos
           - Ms efectos

     As que que les parece? Les parece bien ese orden o les gustara ver
     otras cosas primero? Prefieren 3D o efectos especiales? Ustedes deciden.

     Por cierto, alguien tiene alguna idea de cmo hacer los "blobs"
     (tambin conocidos como "metaballs")???? Si alguien sabe algo, por
     favor manden un e-mail.


Anuncios clasificados:
----------------------

          ----------------------------------------------------------
          |                                                        |
          |                  T M T   P A S C A L                   |
          |                                                        |
          |   TMT es un Pascal para programar en modo protegido    |
          |  de 32 bits,  con lo cual desaparece el lmite de los  |
          |   640 Kb en DOS y se aprovecha toda la memoria de la   |
          |  computadora. Adems, TMT Pascal tiene las siguientes  |
          |                    caractersticas:                    |
          |                                                        |
          |    - Soporta ensamblador en lnea de 32 bits           |
          |    - Soporta instrucciones MMX y VESA 2.0              |
          |    - Soporta arreglos mayores de 64 Kb                 |
          |    - Es 98% compatible con Turbo Pascal                |
          |    - Puede enlazar archivos .OBJ de Turbo Assembler    |
          |    - Es fcil de usar                                  |
          |    - La versin registrada incluye el editor,          |
          |      el debugger, ayuda completa en lnea y ms        |
          |      ejemplos.                                         |
          |                                                        |
          |    Puedes bajar la versin freeware que incluye el     |
          |     compilador y varios ejemplos de alguno de los      |
          |                 siguientes lugares:                    |
          |                                                        |
          |     http://www.tmt.com             (TMT Homepage)      |
          |     http://galia.fc.uaslp.mx/~ganzo/prog/new.html      |
          |                                                        |
          |  * Existe una versin de la unidad MODE13.PAS para     |
          |    TMT Pascal. Aquellos que la quieran, pnganse en    |
          |    contacto conmigo (FAC).                             |
          |                                                        |
          ----------------------------------------------------------
          ----------------------------------------------------------
          |                                                        |
          |    Tienes algo que decir o anunciar al resto de la     |
          |               comunidad programadora?                  |
          |                                                        |
          |             Para eso est este espacio!                |
          |                                                        |
          |     Enva tus anuncios a  fac@slp1.telmex.net.mx       |
          |   con el ttulo "Tutorial - Anuncio" y ser puesto     |
          |                 en el prximo tutorial                 |
          |                                                        |
          |    * Limitado a dos o tres anuncios por tutorial *     |
          |                                                        |
          ----------------------------------------------------------
  -------------------------------------------------------------------------
  |                                                           |
  |                                                          |
  |                         |
  |                  |
  |                          |
  |                      |
  |                  |
  |                   |
  |                    |
  |                                                                       |
  |       El primer (hasta donde sabemos) grupo de demos en Mxico        |
  |                                                                       |
  |  Necesitamos artistas grficos que dibujen a colores, msicos que     |
  |  compongan en MOD y S3M y programadores. Si quieren unirse al grupo,  |
  |  manden una muestra de su trabajo a alguno de los siguientes:         |
  |                                                                       |
  |   fac:   fac@slp1.telmex.net.mx   |  mr. e:   mre@galia.fc.uaslp.mx   |
  |          ganzo@galia.fc.uaslp.mx  |                                   |
  -------------------------------------------------------------------------

Final:  Eso es todo por ahora. Programen mucho.
                                                                - FAC -
