Vous êtes sur la page 1sur 61

Apêndices na Web

Os apêndices a seguir estão disponíveis como documentos PDF no site que acompanha o
livro (www.pearsonhighered.com/deitel/):
1. Apêndice E, Programação de jogos com a biblioteca C Allegro
2. Apêndice F, Classificação: uma visão mais detalhada
3. Apêndice G, Introdução ao padrão C99
4. Apêndice H, Usando o depurador do Visual Studio
5. Apêndice I, Usando o depurador GNU
® ®
Estes arquivos podem ser vistos no Adobe Reader (get.adobe.com/reader). As entradas de
índice para estes apêndices possuem números de página em formato de algarismos romanos.
Cópias novas deste livro vêm com um código de acesso ao site Web, localizado no
cartão por trás da capa do livro. Se o código de acesso já estiver visível ou se não houver
cartão, você comprou um livro usado ou uma edição que não vem com um código de
acesso. Nesse caso, você pode adquirir o acesso diretamente do site Web que acompanha o
livro.
Apêndice E

Programação de jogos com a biblioteca C Allegro


Uma imagem vale mais que mil palavras.
—Provérbio chinês
Trate a natureza em termos do cilindro, da esfera e do cone, tudo em perspectiva.
—Paul Cézanne
Nada se tornará real até que seja experimentado até um provérbio não é um provérbio
até que sua vida o tenha ilustrado.
—John Keats

Objetivos
Neste capítulo, você aprenderá:
Como instalar a biblioteca de programação de jogos Allegro para que funcione com
seus programas em C.
A criar jogos usando o Allegro.
A importar e exibir gráficos.
A usar o ―buffering duplo‖ para criar animações suaves.
A importar e tocar sons.
A reconhecer e processar entrada do teclado.
A criar um jogo de Pong simples.
A usar timers para regular a velocidade de um jogo.
A usar datafiles de dados para encurtar a quantidade de código em um programa.

Esboço
E.1 Introdução
E.2 Instalando o Allegro
E.3 Um programa Allegro simples
E.4 Gráficos simples: Importando mapas de bits e blitando
E.5 Animação com buffering duplo
E.6 Importando e tocando sons
E.7 Entrada pelo teclado
E.8 Fontes e exibição de texto
E.9 Implementando o jogo de Pong
E.10 Timers no Allegro
E.11 O Grabber e datafiles do Allegro
E.12 Outras capacidades do Allegro
E.13 Allegro Resource Center
Resumo | Terminologia | Exercícios de autorrevisão | Respostas dos exercícios de autorrevisão | Exercícios

E.1 Introdução
Apresentamos agora a programação de jogos e gráficos com a biblioteca C Allegro. Criada
em 1995 pelo programador de jogos da Climax, Shawn Hargreaves, Allegro agora é um
projeto de fonte aberto mantido pela comunidade de desenvolvedores de jogos em
www.allegro.cc.

Neste apêndice, mostraremos como usar a biblioteca C Allegro para criar um jogo
de Pong simples. Demonstraremos como exibir gráficos e animar suavemente objetos em
movimento. Explicaremos recursos adicionais, como som, entrada do teclado, saída de texto
e timers, que são úteis na criação de jogos. O apêndice inclui links na Web para mais de
1.000 jogos Allegro de fonte aberto e tutoriais sobre técnicas avançadas em Allegro.

E.2 Instalando o Allegro


O Allegro foi criado para ser uma biblioteca de programação relativamente simples, mas
sua instalação pode ser complicada. Aqui, explicaremos a instalação usando o Visual C++
2008 Express Edition no Windows e GCC 4.3 no Linux. O download completo do código-
fonte do Allegro contém instruções para compilação a partir da origem para uma grande
variedade de plataformas no diretório docs/build.
Instalando o Allegro no Windows
Para começar, você precisa da própria biblioteca Allegro, que está disponível em
www.allegro.cc/files. Baixe o Allegro 4.2.2 pré-compilado para Microsoft Visual C++ 9.0 e o
arquivo zip contendo as ferramentas e exemplos. Extraia esses arquivos em um local
conveniente, como
C:\Allegro.

No Visual Studio 2008, vá até Tools > Options > Projects and Solutions > VC++
Directories e selecione Include files na combo Show Directories for:. Selecione uma linha em
branco e navegue pela pasta include incluída com o download do Allegro. Em seguida,
selecione Library files pela combo e navegue até a pasta lib. Finalmente, copie todos os
arquivos .dll da pasta bin incluída com o download do Allegro para a pasta C:\Windows\System32.
Quando a instalação estiver completa, você terá de dizer ao Visual Studio onde
encontrar a biblioteca Allegro quando criar um novo projeto. Para fazer isso, crie um novo
Win32 Project. No Win32 Application Wizard, selecione Windows application como tipo de
aplicação e marque a caixa Empty project. Quando o projeto tiver sido criado, vá até Project >
Properties, depois Configuration Properties > Linker > Input. Selecione Additional Dependencies
e inclua ―alleg.lib‖. Você precisa realizar essa etapa para cada projeto Allegro que criar. A
pasta de exemplos deste apêndice inclui um projeto vazio e a solução com a biblioteca
Allegro já acrescentada como uma dependência, que você poderá usar para executar os
programas.
Instalando o Allegro no Linux
A maioria das distribuições Linux possui pacotes pré-compilados para o Allegro, embora o
nome exato do pacote possa ser diferente. No Debian e em distribuições baseadas no
Debian, como Ubuntu, o pacote é liballegro4.2-dev; no Fedora ele se chama allegro; e no
openSUSE ele é dividido nos pacotes allegro-devel e allegro-tools. Instale o(s) pacote(s)
apropriado(s) e o pacote make usando o gerenciador de pacotes da sua distribuição (por
exemplo, Synaptic, PackageKit ou YaST).
O programa make torna a compilação mais fácil, permitindo que você armazene
instruções para compilar programas em um arquivo chamado Makefile, em vez de digitá-los na
linha de comandos toda vez que você criar o programa. Fornecemos um Makefile com os
exemplos para este apêndice simplesmente executar ―make‖ de dentro do diretório usará o
Makefile para compilar todos os exemplos no apêndice. Se você estiver criando seu próprio
projeto, poderá adaptar nosso Makefile ou usar o programa allegro-config, que mostra as opções
do compilador necessárias para compilar e ligar os programas Allegro.

E.3 Um programa Allegro simples


Considere um programa Allegro simples, que mostra uma mensagem (Figura E.1). Além da
diretiva do pré-processador para incluir o cabeçalho allegro.h na linha 3 (essa diretiva precisa
estar presente para um programa Allegro operar corretamente), você notará várias funções
nesse programa que não foram usadas anteriormente. A chamada de função allegro_init (linha
7) inicializa a biblioteca Allegro. Ela é necessária para um programa Allegro funcionar, e
precisa ser chamada antes de quaisquer outras funções Allegro.
1 /* Figura E.1: figE_01.c

2 Um programa Allegro simples. */

3 #include <allegro.h>
4
5 int main( void )

6 {

7 allegro_init(); /* inicializa Allegro */

8 allegro_message( "Bem vindo ao Allegro!" ); /* mostra uma mensagem */

9 return 0;

10 } /* fim da função main */


11 END_OF_MAIN() /* Macro específica do Allegro */

[F]Figura E.1 Um programa Allegro simples.


A chamada a allegro_message (linha 8) mostra uma mensagem para o usuário na forma
de uma caixa de alerta. Como as caixas de alerta interrompem os programas em execução,
você provavelmente não usará essa função muitas vezes é preferível usar as funções de
exibição de texto do Allegro para exibir mensagens na tela sem interromper o programa. A
função allegro_message normalmente é usada para notificar o usuário se algo estiver impedindo
o correto funcionamento do Allegro.
O uso da macro END_OF_MAIN é o último dos novos elementos nesse programa. O
Windows, alguns sistemas Unix e Mac OS X podem executar programas Allegro sem essa
macro, pois o executável que o programa Allegro cria durante a compilação não será capaz
de iniciar corretamente nesses sistemas. A macro END_OF_MAIN verifica o sistema operacional
atualmente em execução durante a compilação e aplica um reparo apropriado para esse
problema, se for necessário. Em sistemas diferentes daqueles listados anteriormente,
END_OF_MAIN não fará nada, e seu programa será compilado e executará perfeitamente sem
ele. Porém, você ainda deverá incluir a macro após a chave direita do seu main, para garantir
que seu programa seja compatível com sistemas que o exijam.
Assim, para que qualquer programa Allegro funcione, você precisa ter três linhas de
código a diretiva do pré-processador para incluir allegro.h, a chamada para allegro_init e (se
seu sistema precisar) a chamada para a macro END_OF_MAIN após a chave direita da função
main do seu programa. Agora, vamos discutir a principal capacidade do Allegro exibir
gráficos.

E.4 Gráficos simples: Importando mapas de bits e blitando


O Allegro pode desenhar linhas e formas simples por conta própria, mas a maioria dos
gráficos que ele pode exibir vem de fontes externas. O cabeçalho allegro.h define vários tipos
que podem ser usados para manter dados de imagem de arquivos externos o tipo
ponteiro BITMAP* é o mais básico deles. Um BITMAP* aponta para uma struct na memória onde
os dados da imagem são armazenados. O Allegro tem muitas funções que podem ser usadas
para manipular bitmaps. As mais importantes delas aparecem na Figura E.2.
Protótipo de função Descrição
BITMAP *create_bitmap(int width, int height) Cria e retorna um ponteiro para um bitmap em branco
com largura e altura especificadas (em pixels).
BITMAP *load_bitmap(const char *filename, Carrega e retorna um ponteiro para um mapa de bits a
RGB *pal) partir do local especificado em filename com palheta pal.
void clear_bitmap(BITMAP *bmp) Limpa os dados de imagem de um bitmap e o deixa
em branco.
void clear_to_color(BITMAP *bmp, int color) Limpa os dados de imagem de um bitmap e torna o
bitmap inteiro da cor especificada.
void destroy_bitmap(BITMAP *bmp) Destrói um bitmap e libera a memória alocada a ele
anteriormente. Use esta função quando você terminar
de usar um bitmap, para impedir perdas de memória.
[F]Figura E.2 Funções de BITMAP importantes.
Agora, vamos usar as funções do Allegro para carregar um bitmap e exibi-lo na tela
até que uma tecla seja pressionada. Primeiro, precisamos de um bitmap para carregar,
portanto, copie o bitmap picture.bmp incluído nos exemplos deste apêndice e salve-o na mesma
pasta do seu projeto Allegro. O código na Figura E.3 mostra o bitmap até que uma tecla seja
pressionada.
Erro comum de programação E.1
Dizer ao Allegro para carregar um arquivo que não está na mesma pasta do
programa sendo executado causará um erro de runtime, a menos que você diga
especificamente ao programa a pasta em que o arquivo está localizado, digitando o
nome do caminho completo.
1 /* Figura E.3: figE_03.c

2 Exibindo um bitmap na tela. */

3 #include <allegro.h>
4
5 int main( void )

6 {

7 BITMAP *bmp; /* ponteiro para o bitmap */


8
9 allegro_init(); /* inicializa o Allegro */

10 install_keyboard(); /* permite que o Allegro receba entrada do teclado */

11 set_color_depth( 16 ); /* define a profundidade de cor para 16 bits */

12 set_gfx_mode( GFX_AUTODETECT, 640, 480, 0, 0 ); /* define modo gráfico */

13 bmp = load_bitmap( "picture.bmp", NULL ); /* carrega arquivo de bitmap */

14 blit( bmp, screen, 0, 0, 0, 0, bmp->w, bmp->h ); /* desenha o bitmap */

15 readkey(); /* espera tecla ser pressionada */

16 destroy_bitmap( bmp ); /* libera a memória alocada ao bmp */

17 return 0;

18 } /* fim da função main */


19 END_OF_MAIN() /* macro específica do Allegro */

[F]Figura E.3 Exibindo um bitmap na tela.


Primeiro, este programa inicializa a biblioteca Allegro e acrescenta a capacidade de
ler entrada do teclado. Depois, ele define o modo gráfico do programa. Finalmente, ele
carrega picture.bmp, o apresenta no canto superior esquerdo da tela, depois espera uma tecla
pressionada. Quando isso acontece, o programa libera a memória que estava alocada ao
bitmap e depois termina.
Existem muitas funções novas do Allegro neste programa. A chamada a install_keyboard
na linha 10 ―instala‖ o teclado, de modo que o Allegro possa reconhecê-lo e usá-lo. Ela não
tem nada a ver com a exibição do nosso bitmap, mas precisa ser chamada para que o
Allegro saiba como ―esperar‖ uma tecla pressionada após o bitmap ser exibido.
A linha 11 chama nossa primeira função relacionada a gráficos, set_color_depth, que
define a profundidade de cor da tela para n bits, onde n é o int passado à função. Uma
profundidade de cor de n bits significa que existem 2n cores possíveis que podem ser
exibidas. Uma profundidade de cor menor exigirá menos memória para o programa, mas
sua aparência pode não ser tão boa quanto uma profundidade maior. Embora tenhamos
definido a profundidade de cores para 16 bits em nosso programa, você pode usar uma
profundidade de cor diferente, se quiser, mas os únicos valores que set_color_depth aceita são 8,
15, 16, 24 e 32. Porém, aconselhamos que você evite usar uma profundidade de 8 bits até
que esteja mais experiente com o Allegro. Se a profundidade de cor for definida para 8 bits,
o carregamento de um bitmap também exige que você carregue a palheta do bitmap em
outras palavras, o conjunto de cores que o bitmap usa , que é um processo fora do escopo
deste apêndice. Uma profundidade de cor de pelo menos 16 bits é recomendada. Uma
profundidade de 15 bits também é aceitável, mas nem todas as plataformas a admitem.
Na linha 12, chamamos outra função relacionada a gráficos, set_gfx_mode. Esta
chamada define o modo gráfico do programa. Essa é uma função importante; vamos
examinar seu protótipo.
int set_gfx_mode(int card, int width, int height, int v_w, int v_h);

Se a função tiver sucesso, ela retorna 0; caso contrário, ela retorna um valor diferente de 0.
Em geral, a maioria das funções do Allegro que podem falhar segue o mesmo paradigma.
Se você quiser, poderá inserir estruturas if em nosso programa para verificar se suas funções
funcionam corretamente e dizer ao programa como prosseguir se uma delas falhar, mas,
para economizar espaço, nossos programas de exemplo considerarão que todas as nossas
funções funcionam conforme deveriam.
O primeiro parâmetro da função set_gfx_mode é int card. Nas versões mais antigas do
Allegro, esse parâmetro era usado para dizer ao computador qual driver de placa de vídeo
utilizar, mas determinar qual driver funcionaria corretamente com um certo sistema e
programa era um processo difícil. Nas versões mais novas da biblioteca, diversos dos
chamados ―drivers mágicos‖ foram acrescentados para detectar os drivers do computador
automaticamente GFX_AUTODETECT, GFX_AUTODETECT_FULLSCREEN, GFX_AUTODETECT_WINDOWED
e GFX_SAFE (todos estes são constantes simbólicas definidas em allegro.h). Além de especificar
o driver a usar, passar ao parâmetro um dos valores anteriores também diz ao Allegro se ele
deve executar o programa em uma janela ou se o programa deve usar a tela cheia. Passar
GFX_AUTODETECT_FULLSCREEN ou GFX_AUTODETECT_WINDOWED diz ao programa para executar no
modo de tela cheia (o programa ocupa a tela inteira) ou no modo em janelas (o programa é
executado em uma janela padrão), respectivamente. Passar GFX_AUTODETECT faz o programa
tentar o modo de tela cheia primeiro, depois o modo em janela, se o modo de tela cheia
causar um erro. O modo GFX_SAFE geralmente não é usado; embora ele atue da mesma forma
que GFX_AUTODETECT, há um acréscimo. Se os modos de tela cheia e em janela falharem, o
modo GFX_SAFE força o computador a usar opções que ele ―sabe‖ que funcionarão. Porém,
esses modos ―seguros‖ normalmente possuem resolução e profundidade de cor
extremamente baixas, de modo que geralmente são evitados. Há também uma quinta
constante simbólica, GFX_TEXT, mas essa opção só permite texto, como o nome sugere, e
passar essa constante a set_gfx_mode basicamente ―desativará‖ qualquer gráfico em janela ou
em tela cheia que já estiver rodando.
Observação de engenharia de software E.1
Evite usar o “driver mágico” GFX_SAFE, se possível. Os modos gráficos “seguros”
geralmente possuem um impacto negativo sobre a aparência do seu programa.
Os dois parâmetros seguintes, width e height, determinam o número de pixels na largura
e altura da tela (ou da janela, se você estiver usando o modo em janela), respectivamente.
Os dois últimos parâmetros (v_w e v_h) definem a largura e altura mínima em pixels,
respectivamente, do que é chamada de ―tela virtual‖. A ―tela virtual‖ foi usada nas versões
anteriores do Allegro para ajudar a criar jogos onde a ação pode ocorrer fora da visão da
tela visível, mas não tem qualquer uso real na versão atual do Allegro, pois a maioria dos
sistemas hoje não admite isso. Assim, os parâmetros v_w e v_h devem simplesmente receber o
valor 0.
Agora que sabemos como definir o modo gráfico, vamos examinar a função na linha
13, load_bitmap. Essa função, apresentada na Figura E.2, carrega a figura que foi salva como
picture.bmp e faz com que o ponteiro bmp aponte para ela. Não passamos uma palheta à função
isso não é necessário. Lembre-se de que passar uma palheta do bitmap só é necessário
quando a profundidade de cor é definida como 8 bits, porém, é importante que você cuide
de definir a profundidade de cor e o modo gráfico no seu programa antes de tentar carregar
um arquivo de bitmap. Caso contrário, o Allegro não terá informações sobre como
armazenar o bitmap na memória (ele não saberá quantos bits na memória deverá usar para
cada pixel, por exemplo) e, ao invés disso, tentará ―adivinhar‖ como proceder. Isso pode
ocasionar vários erros no seu programa.

Erro comum de programação E.2


Carregar um bitmap antes de definir a profundidade de cor e o modo gráfico de um
programa provavelmente resultará no Allegro armazenando o bitmap
incorretamente.
Se load_bitmap falhar se, por exemplo, você lhe disser para carregar um bitmap que
não existe , então ele não causará realmente um erro nessa linha. Em vez disso, dizer a
load_bitmap para carregar um arquivo inexistente simplesmente faz com que a função retorne o
valor NULL. Obviamente, isso pode causar problemas mais adiante no programa, mas os
compiladores não consideram uma chamada com falha a load_bitmap como um erro. Em geral,
qualquer função do Allegro que carregue um arquivo externo se comporta da mesma
maneira.
A linha 14 contém uma chamada àquela que provavelmente é a função mais
importante neste programa, blit. A blit (blit significa BLock Transfer o ―i‖ existe apenas
para torná-lo pronunciável) é uma função do Allegro que apanha um bloco de um bitmap (o
bloco pode ser a imagem inteira, se você quiser) e o desenha em outro. Considere este
protótipo de função:
void blit(BITMAP *source, BITMAP *dest, int source_x, int source_y, int dest_x, int dest_y, int width, int height);

Como blit desenha um bitmap em outro, temos de especificar esses bitmaps para que a
função funcione. Os dois primeiros parâmetros (source e dest) definem os bitmaps de origem e
destino, respectivamente, de modo que, em nosso programa, estamos apanhando um bloco
de bmp e desenhando-o em screen. A constante simbólica screen (definida em allegro.h) refere-se
à tela do computador. O blitting na tela é um modo de exibir gráficos.
Como o blitting apanha um bloco de um bitmap, também temos de especificar a
posição desse bloco no bitmap de origem. Os parâmetros source_x e source_y especificam as
coordenadas do canto superior esquerdo do bloco que queremos desenhar no bitmap de
destino. As coordenadas de bitmap do Allegro não funcionam da mesma maneira que na
maioria dos gráficos em nossas aulas de álgebra e geometria: um valor x maior significa
mais à direita, mas um valor y maior significa mais para baixo, e não para cima. Isso
significa que o canto superior esquerdo de qualquer imagem de bitmap está nas coordenadas
(0, 0). A Figura E.4 ilustra esse sistema de coordenadas usando a saída do programa que
escrevemos na Figura E.3.
[F]Figura E.4 Sistema de coordenadas do Allegro.
Para desenhar em um bitmap, também temos de dizer à função onde queremos que o
desenho ocorra. Os parâmetros dest_x e dest_y determinam as coordenadas do ponto superior
esquerdo desse local. Novamente, as coordenadas (0, 0) representam o canto superior
esquerdo de um bitmap.
Finalmente, embora tenhamos especificado o canto superior esquerdo do bloco que
queremos copiar do nosso bitmap de origem, ainda não especificamos seu tamanho. Os dois
últimos parâmetros especificam a largura (width) e a altura (height) do bloco que estamos
―blitando‖. Você pode estar se perguntando por que, em nosso programa, pudemos passar a
esses parâmetros os valores bmp->w e bmp->h. O motivo para isso é que o tipo BITMAP* definido
pelo Allegro é um ponteiro para uma struct. Junto com os dados de imagem e algumas outras
variáveis, a struct BITMAP contém dois ints, w e h, que representam a largura e a altura do
bitmap, respectivamente. A menos que você não queira mostrar a imagem inteira, deverá
passar a esses parâmetros os valores bmp->w e bmp->h, substituindo bmp pelo nome do seu
bitmap.
A função blit que chamamos na linha 14, então, copia um bloco de bmp, cujo canto
superior esquerdo e tamanho são aqueles de bmp, e o desenha no canto superior esquerdo da
tela virtual. Resumindo, ela mostra a imagem contida em picture.bmp no canto superior
esquerdo da tela.
Depois de blitar a imagem na tela, o programa faz uma chamada à função readkey.
Essa função é o motivo pelo qual chamamos install_keyboard anteriormente no programa. A
função readkey espera que uma tecla seja pressionada, depois retorna o valor da tecla
pressionada como um int. Embora não façamos nada com o valor que ela retorna, essa
função é útil nesse programa porque, como scanf, ele faz com que o programa pare até que o
usuário forneça alguma entrada. Sem ela, o programa terminaria antes que tivéssemos a
chance de ver o bitmap.
Finalmente, após pressionarmos uma tecla, o programa chama destroy_bitmap.
Conforme já explicamos, essa função destrói o bitmap passado a ela e realiza o equivalente
da função free sobre a memória que foi alocada a ele.
Dica de prevenção de erro E.1
Use a função destroy_bitmap para liberar a memória de um bitmap que não é mais
necessário e impedir perdas de memória.
Erro comum de programação E.3
Tentar destruir um bitmap que não foi inicializado causa um erro em tempo de
execução.

E.5 Animação com buffering duplo


Agora que sabemos como desenhar bitmaps na tela, animar bitmaps e fazê-los movimentar
na tela é simples. Agora, vamos desenvolver o jogo simples ―Pong‖. Começamos usando
técnicas de animação para criar uma ―bola‖ quadrada que quica em uma tela branca. Para
essa finalidade, precisamos de um bitmap que sirva como nossa ―bola‖. Copie o arquivo
ball.bmp da pasta de exemplos deste apêndice e salve-o em um novo projeto, chamado pong. A
imagem ball.bmp tem 40 pixels de largura por 40 pixels de altura isso será importante
enquanto codificamos o programa.
Na maioria dos jogos de Pong, a bola pode trafegar em muitos ângulos diferentes.
Porém, como estamos apenas começando a usar o Allegro, queremos manter as coisas o
mais simples possível. Por esse motivo, em nosso jogo de Pong, a bola só tem quatro
direções possíveis de trajeto: abaixo à direita, acima à direita, abaixo à esquerda e acima à
esquerda todas estas em ângulos de 45 graus em relação aos eixos x e y em nosso
programa. Usamos um int para registrar a direção atual da bola, de modo que podemos usar
constantes simbólicas para cada uma das possíveis direções.
Mover um bitmap pela tela é simples apenas apresentamos nosso bitmap na tela e
depois, quando quisermos movê-lo, apagamos a tela e apresentamos o bitmap em sua nova
posição. O programa na Figura E.5 cria uma bola que se movimenta e quica nas bordas da
tela até que pressionemos a tecla Esc.
1 /* Figura E.5: figE_05.c

2 Criando a bloca que quica. */

3 #include <allegro.h>
4
5 /* Constantes simbólicas para as direções de posição da bola */

6 #define DOWN_RIGHT 0

7 #define UP_RIGHT 1

8 #define DOWN_LEFT 2

9 #define UP_LEFT 3
10
11 /* protótipos de função */
12 void moveBall( void );
13 void reverseVerticalDirection( void );
14 void reverseHorizontalDirection( void );
15
16 int ball_x; /* a coordenada-x da bola */
17 int ball_y; /* a coordenada-y da bola */
18 int direction; /* a direção da bola */
19 BITMAP *ball; /* ponteiro para o bitmap de imagem da bola */
20
21 int main( void )
22 {
23 /* primeiro, configura o Allegro e o modo gráfico */

24 allegro_init(); /* inicializa o Allegro */

25 install_keyboard(); /* instala o teclado para o Allegro usar */

26 set_color_depth( 16 ); /* define a profunidade de cor em 16 bits */

27 set_gfx_mode( GFX_AUTODETECT, 640, 480, 0, 0 ); /* define o modo gráfico */

28 ball = load_bitmap( "ball.bmp", NULL ); /* carrega o bitmap da bola */

29 ball_x = SCREEN_W / 2; /* dá à bola sua coordenada-x inicial */

30 ball_y = SCREEN_H / 2; /* dá à bola sua coordenada-y inicial */

31 srand( time( NULL ) ); /* gera semente aleatória da função */

32 direction = rand() % 4; /* depois cria uma direção inicial aleatória */


33
34 while ( !key[KEY_ESC] ) /* até que a tecla Escape seja pressionada ... */

35 {
36 moveBall(); /* move a bola */

37 clear_to_color( screen, makecol( 255, 255, 255 ) );

38 /* desenha o bitmap na tela */

39 blit( ball, screen, 0, 0, ball_x, ball_y, ball->w, ball->h );

40 } /* fim do while */
41
42 destroy_bitmap( ball ); /* destrói o bitmap da bola */

43 return 0;

44 } /* fim da função main */


45 END_OF_MAIN() /* não se esqueça disto! */
46
47 void moveBall() /* move a bola */
48 {
49 switch ( direction ) {

50 case DOWN_RIGHT:

51 ++ball_x; /* move a bola para a direita */

52 ++ball_y; /* move a bola para baixo */

53 break;

54 case UP_RIGHT:

55 ++ball_x; /* move a bola para a direita */

56 --ball_y; /* move a bola para cima */

57 break;

58 case DOWN_LEFT:

59 --ball_x; /* move a bola para a esquerda */

60 ++ball_y; /* move a bola para baixo */

61 break;

62 case UP_LEFT:

63 --ball_x; /* move a bola para a esquerda */

64 --ball_y; /* move a bola para cima */

65 break;

66 } /* fim do switch */
67
68 /* cuida para que a bola não saia da tela */
69
70 /* se a bola está saindo acima ou abaixo... */

71 if ( ball_y <= 30 || ball_y >= 440 )

72 reverseVerticalDirection(); /* faz seguir na outra direção */


73
74 /* se a bola está saindo para a esquerda ou para a direita... */

75 if ( ball_x <= 0 || ball_x >= 600 )

76 reverseHorizontalDirection(); /* faz seguir na outra direção */


77 } /* fim da função moveBall */
78
79 void reverseVerticalDirection() /* inverte a direção acima-abaixo da bola */
80 {
81 if ( ( direction % 2 ) == 0 ) /* direções "para baixo" são números pares */

82 ++direction; /* faz a bola começar a subir */

83 else /* direções "para cima" são números ímpares */

84 --direction; /* faz a bola começar a descer */

85 } /* fim da função reverseVerticalDirection */


86
87 void reverseHorizontalDirection() /* inverte a direção horizontal */
88 {
89 direction = ( direction + 2 ) % 4; /* inverte a direção horizontal */

90 } /* fim da função reverseHorizontalDirection */

[F]Figura E.5 Criando a bola que quica.


Não há muita coisa nova neste programa. As primeiras linhas de interesse estão nas
linhas 29 e 30, onde definimos os valores iniciais para ball_x e ball_y. As constantes simbólicas
SCREEN_W e SCREEN_H são reservadas pelo Allegro estas correspondem à largura e altura
(em pixels), respectivamente, da tela definida chamando set_gfx_mode. Essas linhas, portanto,
colocam a bola no centro da tela.
A linha 34 contém um novo item relacionado ao teclado o Allegro tem um array
de ints chamado key. O array contém um índice para cada tecla no teclado se uma certa
tecla estiver sendo pressionada, o valor em seu índice correspondente é definido como 1; se
ela não estiver sendo pressionada, o valor é definido como 0. O índice que verificamos no
loop while é KEY_ESC, uma constante simbólica do Allegro que corresponde à tecla Esc. Como
a condição para nosso loop while é !key[KEY_ESC], nosso programa continuará enquanto
key[KEY_ESC] tiver um valor 0 em outras palavras, enquanto a tecla Esc não estiver sendo
pressionada. Discutiremos sobre o array key e as constantes simbólicas do Allegro para o
teclado com mais detalhes na Seção E.7.
A linha 37 contém a função que usamos para desenhar o fundo clear_to_color. O
Allegro não tem qualquer ―fundo‖ explícito definido em sua biblioteca, mas a função
clear_to_color, descrita na Figura E.2, define uma cor inicial para a tela, em que podemos
desenhar mais tarde. Observe, porém, que em nosso programa tivemos de usar uma função
para passar o parâmetro color a clear_to_color. Essa é a função makecol. Seu protótipo é:
int makecol( int red, int green, int blue );

Essa função retorna um int que representa a cor com as intensidades de vermelho
(Red), verde (Green) e azul (Blue) especificadas. As intensidades permitidas podem variar
de 0 a 255, de modo que passar os valores ( 255, 0, 0 ) criará uma cor vermelha brilhante, ( 0,
255, 0 ) criará um verde brilhante e ( 0, 0, 255 ) criará um azul brilhante. Na Figura E.5,
passamos ( 255, 255, 255 ), ou a intensidade máxima de todas as três cores, que cria o branco.
Isso significa que nosso programa definirá a cor da tela como branca quando a função
clear_to_color for chamada. A Figura E.6 mostra uma tabela de cores comuns e seus valores
vermelho, verde e azul.
Cor Valor de Red Valor de Green Valor de Blue

Red 255 0 0
Green 0 255 0
Blue 0 0 255
Orange 255 200 0
Pink 255 175 175
Cyan 0 255 255
Magenta 255 0 255
Yellow 255 255 0
Black 0 0 0
White 255 255 255
Gray 128 128 128
Light gray 192 192 192
Dark gray 64 64 64
[F]Figura E.6 As intensidades de vermelho, verde e azul das cores comuns no Allegro.
O restante do programa é autoexplicativo fazemos a bola se mover pela tela e,
quando ela atinge uma borda, ela muda sua direção de modo que ―quique‖. O motivo para
as linhas em moveBall e as duas funções reverseDirection estarem destacadas é que a matemática
pode parecer um pouco estranha. Primeiro, verificamos se a bola está saindo da tela nas
linhas 71 e 75. Você pode estar se perguntando por que os limites direito e inferior de nossa
tela parecem ser 600 e 440 nessas instruções if, ao invés de 640 e 480 (o tamanho real da
tela). Lembre-se de que, quando blitamos um bitmap na tela, passamos a ele o canto
superior esquerdo do bloco em que queremos desenhar. Isso significa que, se ball_y tem um
valor de 440, seu limite superior será na coordenada y 440. Porém, como a bola tem 40
pixels de altura, seu limite inferior na realidade será na coordenada y 480, que é a borda
inferior da tela. O mesmo se aplica à coordenada x da bola, que explica por que o maior
valor que ela pode ter é 600. Além disso, se você está se perguntando por que o menor valor
y permitido é 30, isso é simplesmente porque usaremos os 30 pixels superiores da tela para
o placar quando acrescentarmos mais ao nosso jogo de Pong.
Considere as linhas que mudam a direção da bola. A linha 82 faz com que a bola
comece a subir se estiver atualmente descendo, enquanto a linha 84 faz o contrário. A linha
89 faz a bola começar a mover para a esquerda se estiver atualmente se movendo para a
direita, e para a direita se estiver atualmente se movendo para a esquerda. Por que isso
funciona? Devido aos valores específicos das constantes simbólicas (linhas 6-9), realizarem
as operações nessas três linhas sempre o levará na direção que você deseja.
Nossa ―bola‖ é um quadrado, e não um círculo. Isso não tem muito impacto sobre
nosso programa agora, mas, quando acrescentarmos raquetes em nosso jogo, ter uma bola
quadrada torna muito mais fácil detectar se a bola e as raquetes estão se tocando. Veremos
mais detalhes sobre essa questão na Seção E.9, quando incluirmos esse recurso em nosso
jogo.
Execute o programa e você verá sua bola quicando na tela. Observe, porém, que a
tela pisca muito enquanto a bola se move, ao ponto de ser difícil ver a bola. Para resolver
isso, apresentamos uma técnica chamada buffering duplo.
Buffering duplo para a animação suave
Se você executasse o programa da seção anterior, provavelmente notaria que a tela piscava
enquanto a bola se movia. Por que isso acontece? Lembre-se de que, para nossa animação
funcionar, tivemos de apagar a tela toda vez que a bola se movia. Infelizmente, essa não é a
melhor prática. Embora a maioria dos computadores possa apagar e redesenhar a tela
rapidamente, ainda existe algum tempo em que a tela fica em branco entre o momento no
qual é apagada e quando a bola é impressa nela. Embora a tela esteja vazia apenas
rapidamente, ainda é suficiente para fazer com que a tela pareça piscar enquanto move a
bola, pois a bola continua se apagando antes de ser redesenhada.
Podemos consertar isso com uma técnica chamada buffering duplo, que usa um
bitmap intermediário, do tamanho da tela, chamado de buffer, para tornar a animação dos
bitmaps em movimento mais suave. Em vez de apresentarmos os bitmaps na tela,
imprimimos os objetos que queremos que o usuário veja no buffer. Quando tudo o que
quisermos estiver lá, imprimimos o buffer na tela e apagamos o buffer.
Por que isso funciona? Você notará que nunca apagamos a tela quando usamos essa
técnica. Em vez de apagar tudo antes de redesenhar as imagens na tela, simplesmente
desenhamos sobre o que já está lá, o que elimina as piscadas que o programa na Figura E.5
produziu quando apagou a tela. Além disso, como o buffer não é visível ao usuário (lembre-
se de que o usuário só pode ver a screen), podemos imprimir e apagar o buffer sem nos
preocuparmos se isso afeta algo que o usuário vê. A Figura E.7 mostra essa técnica na
prática.
1 /* Figura E.7: figE_07.c

2 Usando o buffering duplo. */

3 #include <allegro.h>
4
5 /* Constantes simbólicas para as direções de posição da bola */

6 #define DOWN_RIGHT 0

7 #define UP_RIGHT 1

8 #define DOWN_LEFT 2

9 #define UP_LEFT 3
10
11 /* protótipos de função */

12 void moveBall( void );

13 void reverseVerticalDirection( void );

14 void reverseHorizontalDirection( void );


15
16 int ball_x; /* a coordenada-x da bola */

17 int ball_y; /* a coordenada-y da bola */

18 int direction; /* a direção da bola */

19 BITMAP *ball; /* ponteiro para o bitmap de imagem da bola */

20 BITMAP *buffer; /* ponteiro para o buffer */


21
22 int main( void )

23 {

24 /* primeiro, configura o Allegro e o modo gráfico */

25 allegro_init(); /* inicializa o Allegro */

26 install_keyboard(); /* instala o teclado para o Allegro usar */

27 set_color_depth( 16 ); /* define a profunidade de cor em 16 bits */

28 set_gfx_mode( GFX_AUTODETECT, 640, 480, 0, 0 ); /* define o modo gráfico */

29 ball = load_bitmap( "ball.bmp", NULL ); /* carrega o bitmap da bola */

30 buffer = create_bitmap( SCREEN_W, SCREEN_H ); /* cria o buffer */

31 ball_x = SCREEN_W / 2; /* dá à bola sua coordenada-x inicial */

32 ball_y = SCREEN_H / 2; /* dá à bola sua coordenada-y inicial */

33 srand( time( NULL ) ); /* gera semente aleatória da função ... */

34 direction = rand() % 4; /* depois cria uma direção inicial aleatória */


35
36 while ( !key[KEY_ESC] ) /* até que a tecla Escape seja pressionada ... */

37 {

38 moveBall(); /* move a bola */

39 /* agora, realiza o buffering duplo */

40 clear_to_color( buffer, makecol( 255, 255, 255 ) );

41 blit( ball, buffer, 0, 0, ball_x, ball_y, ball->w, ball->h );

42 blit( buffer, screen, 0, 0, 0, 0, buffer->w, buffer->h );

43 clear_bitmap( buffer );

44 } /* fim do while */
45
46 destroy_bitmap( ball ); /* destrói o bitmap da bola */

47 destroy_bitmap( buffer ); /* destrói o bitmap do buffer */

48 return 0;

49 } /* fim da função main */

50 END_OF_MAIN() /* não se esqueça disto! */


51
52 void moveBall() /* move a bola */

53 {

54 switch ( direction ) {

55 case DOWN_RIGHT:

56 ++ball_x; /* move a bola para a direita */

57 ++ball_y; /* move a bola para baixo */

58 break;

59 case UP_RIGHT:

60 ++ball_x; /* move a bola para a direita */

61 --ball_y; /* move a bola para cima */

62 break;
63 case DOWN_LEFT:

64 --ball_x; /* move a bola para a esquerda */

65 ++ball_y; /* move a bola para baixo */

66 break;

67 case UP_LEFT:

68 --ball_x; /* move a bola para a esquerda */

69 --ball_y; /* move a bola para cima */

70 break;

71 } /* fim do switch */
72
73 /* cuida para que a bola não saia da tela */
74
75 /* se a bola está saindo acima ou abaixo ... */

76 if ( ball_y <= 30 || ball_y >= 440 )

77 reverseVerticalDirection();
78
79 /* se a bola está saindo para a esquerda ou para a direita ... */

80 if ( ball_x <= 0 || ball_x >= 600 )

81 reverseHorizontalDirection();

82 } /* fim da função moveBall */


83
84 void reverseVerticalDirection() /* inverte a direção acima-abaixo da bola */

85 {

86 if ( ( direction % 2 ) == 0 ) /* direções "para baixo" são números pares */

87 ++direction; /* faz a bola começar a subir */

88 else /* direções "para cima" são números ímpares */

89 --direction; /* faz a bola começar a descer */

90 } /* fim da função reverseVerticalDirection */


91
92 void reverseHorizontalDirection() /* inverte a direção horizontal */

93 {

94 direction = ( direction + 2 ) % 4; /* inverte a direção horizontal */

95 } /* fim da função reverseHorizontalDirection */

[F]Figura E.7 Usando o buffering duplo.


O buffering duplo usa um pouco mais de código do que o blitting diretamente na
tela nosso programa modificado tem apenas quatro linhas de código a mais do que o
original! As linhas 40-43 mostram como codificar o buffering duplo. Simplesmente
tornamos o buffer branco e blitamos a bola no buffer, na posição ball_x, ball_y. Depois,
blitamos o buffer inteiro na tela e apagamos o buffer. A tela nunca é apagada, somente
desenhada, de modo que não há piscadas.
E.6 Importando e tocando sons
Embora um jogo apenas com gráficos seja divertido, um jogo que usa sons para melhorar a
experiência do jogador é muito mais interessante. Agora, discutiremos a importação de sons
e a reprodução de arquivos de som nos programas em Allegro, que usaremos para
―incrementar‖ nosso jogo com um som de ―boing‖ tocando sempre que a bola atinge uma
parede.
Os arquivos de som são tratados de modo semelhante aos bitmaps assim como o
cabeçalho allegro.h define vários tipos que são usados para manter dados da imagem, ele
também define vários tipos que são usados para manter dados de som. Com as imagens, o
mais básico desses tipos é BITMAP*. Com sons, o mais básico desses tipos é o tipo SAMPLE* —
o Allegro refere-se aos arquivos de sons como ―amostras digitais‖. Assim como BITMAP*, o
tipo SAMPLE* é um ponteiro.
As funções do Allegro usadas para importar e tocar sons são semelhantes àquelas
usadas para importar e exibir bitmaps. A Figura E.8 mostra as funções mais importantes do
Allegro para manipular arquivos de som.
Protótipo de função Descrição
SAMPLE *load_sample(const Carrega e retorna um ponteiro para um
char *filename) arquivo de som com o nome de arquivo
especificado. O arquivo precisa estar no
formato .wav. Retorna NULL (sem erro) se o
arquivo especificado não puder ser carregado.
int play_sample(const SAMPLE *spl, Toca a amostra especificada no volume,
int vol, int pan, int freq, posição de pan e frequência especificados. A
amostra fará um loop contínuo se loop for
int loop)
diferente de 0.
void adjust_sample(const SAMPLE *spl, Ajusta os parâmetros de uma amostra
int vol, int pan, int freq, atualmente tocando aos valores especificados.
Pode ser chamada sobre qualquer amostra sem
int loop)
causar erros, mas afetará apenas aquelas que
estejam tocando atualmente.
void stop_sample(const SAMPLE *spl)
Termina uma amostra que está atualmente
tocando.
void destroy_sample(SAMPLE *spl)
Destrói uma amostra e libera a memória
alocada a ela. Se a amostra estiver atualmente
tocando ou em looping, ela terminará
imediatamente.
[F]Figura E.8 Funções importantes do tipo SAMPLE.
A função install_sound precisa ser chamada antes que o Allegro possa tocar quaisquer
arquivos de som. Seu protótipo é:
int install_sound(int digi, int midi, const char *cfg_path);

A função retorna um int para fins de verificação de erro 0 se a função tiver sucesso e um
valor diferente de 0 em caso contrário. Os parâmetros digi e midi especificam os drivers de
placa de som usados para tocar amostras digitais e arquivos MIDI, respectivamente. Assim
como os drivers gráficos, as versões mais novas do Allegro oferecem os chamados ―drivers
mágicos‖ que automaticamente especificam os drivers de áudio a usar DIGI_AUTODETECT e
MIDI_AUTODETECT. Estes são os únicos valores que você deverá passar aos dos primeiros
parâmetros.
O parâmetro cfg_path não tem efeito sobre o programa. Versões mais antigas do
Allegro exigiam que você especificasse um arquivo .cfg que dizia ao programa como tocar
arquivos de som, mas isso não é mais necessário na versão atual.
Agora, vamos acrescentar sons ao programa da bola que quica. Assim como
bitmaps, temos de fornecer ao programa um arquivo de som externo para carregar; portanto,
copie o arquivo de som boing.wav dos exemplos do apêndice e salve-o na mesma pasta do seu
projeto pong. Depois, podemos usar o código na Figura E.9 para fazer nossa bola emitir um
som de ―boing‖ sempre que ela quicar em uma margem da tela. As linhas destacadas
marcam as mudanças da seção anterior.
1 /* Figura E.9: figE_09.c

2 Usando o buffering duplo. */

3 #include <allegro.h>
4
5 /* Constantes simbólicas para as direções de posição da bola */

6 #define DOWN_RIGHT 0

7 #define UP_RIGHT 1

8 #define DOWN_LEFT 2

9 #define UP_LEFT 3
10
11 /* protótipos de função */
12 void moveBall( void );
13 void reverseVerticalDirection( void );
14 void reverseHorizontalDirection( void );
15
16 int ball_x; /* a coordenada-x da bola */
17 int ball_y; /* a coordenada-y da bola */
18 int direction; /* a direção da bola */
19 BITMAP *ball; /* ponteiro para o bitmap de imagem da bola */
20 BITMAP *buffer; /* ponteiro para o buffer */
21 SAMPLE *boing; /* ponteiro para arquivo de som */
22
23 int main( void )
24 {
25 /* primeiro, configura o Allegro e o modo gráfico */

26 allegro_init(); /* inicializa o Allegro */

27 install_keyboard(); /* instala o teclado para o Allegro usar */

28 install_sound( DIGI_AUTODETECT, MIDI_AUTODETECT, NULL );

29 set_color_depth( 16 ); /* define a profunidade de cor em 16 bits */


30 set_gfx_mode( GFX_AUTODETECT, 640, 480, 0, 0 ); /* define o modo gráfico */

31 ball = load_bitmap( "ball.bmp", NULL ); /* carrega o bitmap da bola */

32 buffer = create_bitmap( SCREEN_W, SCREEN_H );/* cria o buffer */

33 boing = load_sample( "boing.wav" ); /* carrega o arquivo de som */

34 ball_x = SCREEN_W / 2; /* dá à bola sua coordenada-x inicial */

35 ball_y = SCREEN_H / 2; /* dá à bola sua coordenada-y inicial */

36 srand( time( NULL ) ); /* gera semente aleatória da função ... */

37 direction = rand() % 4; /* depois cria uma direção inicial aleatória */

38 while ( !key[KEY_ESC] ) /* até que a tecla Escape seja pressionada ... */

39 {

40 moveBall(); /* move a bola */

41 /* agora, realiza o buffering duplo */

42 clear_to_color( buffer, makecol( 255, 255, 255 ) );

43 blit( ball, buffer, 0, 0, ball_x, ball_y, ball->w, ball->h );

44 blit( buffer, screen, 0, 0, 0, 0, buffer->w, buffer->h );

45 clear_bitmap( buffer );

46 } /* fim do while */

47 destroy_bitmap( ball ); /* destrói o bitmap da bola */

48 destroy_bitmap( buffer ); /* destrói o bitmap do buffer */

49 destroy_sample( boing ); /* destrói o arquivo de som boing */

50 return 0;

51 } /* fim da função main */


52 END_OF_MAIN() /* não se esqueça disto! */
53
54 void moveBall() /* move a bola */
55 {
56 switch ( direction ) {

57 case DOWN_RIGHT:

58 ++ball_x; /* move a bola para a direita */

59 ++ball_y; /* move a bola para baixo */

60 break;

61 case UP_RIGHT:

62 ++ball_x; /* move a bola para a direita */

63 --ball_y; /* move a bola para cima */

64 break;

65 case DOWN_LEFT:

66 --ball_x; /* move a bola para a esquerda */

67 ++ball_y; /* move a bola para baixo */

68 break;

69 case UP_LEFT:
70 --ball_x; /* move a bola para a esquerda */

71 --ball_y; /* move a bola para cima */

72 break;

73 } /* fim do switch */
74
75 /* cuida para que a bola não saia da tela */
76
77 /* se a bola está saindo acima ou abaixo ... */

78 if ( ball_y <= 30 || ball_y >= 440 )

79 reverseVerticalDirection();
80
81 /* se a bola está saindo para a esquerda ou para a direita ... */

82 if ( ball_x <= 0 || ball_x >= 600 )

83 reverseHorizontalDirection();

84 } /* fim da função moveBall */


85
86 void reverseVerticalDirection() /* inverte a direção acima-abaixo da bola */
87 {
88 if ( ( direction % 2 ) == 0 ) /* direções "para baixo" são números pares */

89 ++direction; /* faz a bola começar a subir */

90 else /* direções "para cima" são números ímpares */

91 --direction; /* faz a bola começar a descer */

92 play_sample( boing, 255, 128, 1000, 0 ); /* toca som "boing" uma vez */

93 } /* fim da função reverseVerticalDirection */


94
95 void reverseHorizontalDirection() /* inverte a direção horizontal */
96 {
97 direction = ( direction + 2 ) % 4; /* inverte a direção horizontal */

98 play_sample( boing, 255, 128, 1000, 0 ); /* toca som "boing" uma vez */

99 } /* fim da função reverseHorizontalDirection */

[F]Figura E.9 Utilizando arquivos de som.


Como colocamos a chamada à função play_sample dentro de nossas funções que
invertem a direção da bola, o som de ―boing‖ tocará sempre que a direção da bola for
invertida em outras palavras, sempre que a bola tocar no limite da tela. Considere o
protótipo da função play_sample:
int play_sample(const SAMPLE *sample, int volume, int pan, int frequency, int loop)

Os parâmetros volume e pan determinam o volume e a posição de pan da amostra sendo


tocada, e os valores passados a elas podem variar de 0 a 255. Um volume 0 significa que o
som será mudo, enquanto um volume de 255 toca o som em volume total. Uma posição de
pan de 128 tocará o som pelos dois alto-falantes igualmente, enquanto um valor menor ou
maior tocará o som mais na direção esquerda ou direita, respectivamente. O parâmetro
frequency, que especifica a frequência em que a amostra será tocada, é um parâmetro cujo
valor é relativo ao invés de absoluto. Um valor de frequency 1000 tocará o som na frequência
em que ele foi gravado, enquanto um valor 2000 tocará a amostra no dobro de sua
frequência normal, o que produz um pitch muito mais alto; um valor de 500 tocará a
amostra em metade de sua frequência normal, o que produz um pitch mais baixo, e assim
por diante. Finalmente, conforme explicamos na Figura E.8, o parâmetro loop fará com que a
amostra faça um loop contínuo se seu valor não for 0. Caso contrário, a amostra tocará
apenas uma vez.

E.7 Entrada pelo teclado


Um jogo não pode ser chamado de jogo a menos que o usuário possa interagir com ele de
alguma maneira. Já usamos alguma entrada do teclado neste apêndice; agora, discutiremos a
entrada do teclado no Allegro com mais detalhes.
A primeira coisa que devemos fazer para permitir que o Allegro reconheça e use o
teclado é chamar a função install_keyboard, que não usa parâmetros. O Allegro não precisa de
qualquer informação de driver para instalar o teclado.
Lembre-se de que o Allegro define um array de ints chamado key. Esse array nos
permite determinar quando as teclas são pressionadas. Além do array, o cabeçalho allegro.h
define constantes simbólicas que correspondem às teclas em um teclado padrão. Por
exemplo, a constante para a tecla A é KEY_A, e a constante para a barra de espaço é
KEY_SPACE. A lista completa dessas constantes está disponível em www.allegro.cc/manual/key. Essas
constantes são usadas em conjunto com o array key para determinar se a tecla à qual uma
constante qualquer corresponde está sendo pressionada em determinado momento.
Cada constante simbólica corresponde a um índice no array key que acompanha se
essa tecla está sendo pressionada ou não. Se a tecla não estiver sendo pressionada, o array
manterá 0 nesse índice, e se estiver pressionada, o valor nesse índice será diferente de 0.
Assim, se quisermos ver se a tecla A está sendo pressionada, examinaremos o valor
retornado por key[KEY_A]. Se ele não for 0, então sabemos que o usuário está pressionando a
tecla A.
Em nosso programa Pong, usamos esse array para controlar as raquetes nos lados da
tela. Se as teclas A ou Z estiverem sendo pressionadas, a raquete no lado esquerdo deverá se
mover para cima e para baixo, respectivamente. De modo semelhante, se o usuário
pressionar as teclas de seta para cima ou para baixo, a raquete no lado direito deve se mover
na direção correspondente. Para essa finalidade, acrescentamos uma nova função ao nosso
programa, respondToKeyboard, que verifica se qualquer uma dessas quatro teclas está sendo
pressionada e move as raquetes de acordo.
Naturalmente, ainda não desenhamos as raquetes em nosso programa, de modo que
a primeira coisa de que precisamos é um arquivo de bitmap que contenha os dados de
imagem para elas. Assim como o bitmap da bola, você pode achar o arquivo de bitmap para
a raquete na pasta de exemplos deste apêndice. Salve o arquivo como bar.bmp na mesma pasta
do seu projeto Pong (o bitmap contém 20 por 100 pixels usaremos essa informação no
programa). Quando você tiver feito isso, poderá usar o código na Figura E.10 para permitir
que o usuário mova as raquetes com o teclado. Como sempre, novas linhas no programa
aparecem destacadas.
As linhas 101-109 na Figura E.10 mostram como usamos o array key para determinar
se certas teclas estão sendo pressionadas. Em C, qualquer instrução que retorna um valor
diferente de 0 é considerada ―verdadeira‖ se usada na condição de uma estrutura if, de modo
que o array key do Allegro facilita a verificação de teclas pressionadas. Observe, porém, que
a chamada à função respondToKeyboard está dentro do loop while em nosso main (linha 49) isso
é necessário para que a entrada do teclado funcione corretamente. Embora as instruções if
nas linhas 101-109 sejam tudo o que é necessário para verificar se certas teclas foram
pressionadas, cada instrução verifica apenas uma vez por chamada. Como queremos fazer
com que o programa verifique a entrada do teclado constantemente, temos de colocar a
função respondToKeyboard dentro de algum tipo de loop que garanta que o programa a chamará
repetidamente. Isso também acontece para a maioria dos jogos, além do Pong.
1 /* Figura E.10: figE_10.c

2 Acrescentando raquetes e entrada do teclado. */

3 #include <allegro.h>

4
5 /* Constantes simbólicas para as direções de posição da bola */

6 #define DOWN_RIGHT 0

7 #define UP_RIGHT 1

8 #define DOWN_LEFT 2

9 #define UP_LEFT 3

10
11 /* protótipos de função */

12 void moveBall( void );

13 void respondToKeyboard( void );

14 void reverseVerticalDirection( void );

15 void reverseHorizontalDirection( void );

16
17 int ball_x; /* coordenada-x da bola */

18 int ball_y; /* coordenada-y da bola */

19 int barL_y; /* coordenada-y da raquete esquerda */

20 int barR_y; /* coordenada-y da raquete direita */

21 int direction; /* a direção da bola */

22 BITMAP *ball; /* ponteiro para o bitmap de imagem da bola */

23 BITMAP *bar; /* ponteiro para bitmap de imagem da raquete */

24 BITMAP *buffer; /* ponteiro para o buffer */

25 SAMPLE *boing; /* ponteiro para arquivo de som */


26
27 int main( void )

28 {

29 /* primeiro, configura o Allegro e o modo gráfico */

30 allegro_init(); /* inicializa o Allegro */

31 install_keyboard(); /* instala o teclado para o Allegro usar */

32 install_sound( DIGI_AUTODETECT, MIDI_AUTODETECT, NULL );

33 set_color_depth( 16 ); /* define a profunidade de cor em 16 bits */

34 set_gfx_mode( GFX_AUTODETECT, 640, 480, 0, 0 ); /* define o modo gráfico */

35 ball = load_bitmap( "ball.bmp", NULL ); /* carrega o bitmap da bola */

36 bar = load_bitmap( "bar.bmp", NULL); /* carrega o bitmap bar */

37 buffer = create_bitmap( SCREEN_W, SCREEN_H );/* cria o buffer */

38 boing = load_sample( "boing.wav" ); /* carrega o arquivo de som */

39 ball_x = SCREEN_W / 2; /* dá à bola sua coordenada-x inicial */

40 ball_y = SCREEN_H / 2; /* dá à bola sua coordenada-y inicial */

41 barL_y = SCREEN_H / 2; /* dá à raquete esquerda sua coordenada-y inicial */

42 barR_y = SCREEN_H / 2; /* dá à raquete direita sua coordenada-y inicial */

43 srand( time( NULL ) ); /* gera semente aleatória da função ... */

44 direction = rand() % 4; /* depois cria uma direção inicial aleatória */

45
46 while ( !key[KEY_ESC] ) /* até que a tecla Escape seja pressionada ... */

47 {

48 moveBall(); /* move a bola */

49 respondToKeyboard(); /* responde à entrada do teclado */

50 /* agora, realiza o buffering duplo */

51 clear_to_color( buffer, makecol( 255, 255, 255 ) );

52 blit( ball, buffer, 0, 0, ball_x, ball_y, ball->w, ball->h );

53 blit( bar, buffer, 0, 0, 0, barL_y, bar->w, bar->h );

54 blit( bar, buffer, 0, 0, 620, barR_y, bar->w, bar->h );

55 blit( buffer, screen, 0, 0, 0, 0, buffer->w, buffer->h );

56 clear_bitmap( buffer );

57 } /* fim do while */

58
59 destroy_bitmap( ball ); /* destrói o bitmap da bola */

60 destroy_bitmap( bar ); /* destrói o bitmat bar */

61 destroy_bitmap( buffer ); /* destrói o bitmap do buffer */

62 destroy_sample( boing ); /* destrói o arquivo de som boing */

63 return 0;

64 } /* fim da função main */

65 END_OF_MAIN() /* não se esqueça disto! */

66
67 void moveBall() /* move a bola */

68 {

69 switch ( direction ) {

70 case DOWN_RIGHT:

71 ++ball_x; /* move a bola para a direita */

72 ++ball_y; /* move a bola para baixo */

73 break;

74 case UP_RIGHT:

75 ++ball_x; /* move a bola para a direita */

76 --ball_y; /* move a bola para cima */

77 break;

78 case DOWN_LEFT:

79 --ball_x; /* move a bola para a esquerda */

80 ++ball_y; /* move a bola para baixo */

81 break;

82 case UP_LEFT:

83 --ball_x; /* move a bola para a esquerda */

84 --ball_y; /* move a bola para cima */

85 break;

86 } /* fim do switch */

87
88 /* cuida para que a bola não saia da tela */

89
90 /* se a bola está saindo acima ou abaixo ... */

91 if ( ball_y <= 30 || ball_y >= 440 )


92 reverseVerticalDirection();

93
94 /* se a bola está saindo para a esquerda ou para a direita ... */

95 if ( ball_x <= 0 || ball_x >= 600 )

96 reverseHorizontalDirection();

97 } /* fim da função moveBall */

98
99 void respondToKeyboard() /* responde à entrada do teclado */

100 {

101 if ( key[KEY_A] ) /* se A estiver sendo pressionado... */

102 barL_y -= 3; /* ... move a raquete esquerda para cima */

103 if ( key[KEY_Z] ) /* se A estiver sendo pressionado... */

104 barL_y += 3; /* ... move a raquete esquerda para baixo */

105
106 if ( key[KEY_UP] ) /* se a seta para cima estiver sendo pressionada... */

107 barR_y -= 3; /* ... move a raquete direita para cima */

108 if ( key[KEY_DOWN] ) /* se a seta para baixo estiver sendo pressionada... */

109 barR_y += 3; /* ... move a raquete direita para baixo */

110
111 /* cuida para que as raquetes não saiam da tela */

112 if ( barL_y < 30 ) /* se a raquete esquerda estiver saindo por cima */

113 barL_y = 30;

114 else if ( barL_y > 380 ) /* se a raquete esquerda estiver saindo por baixo */

115 barL_y = 380;

116 if ( barR_y < 30 ) /* se a raquete direita estiver saindo por cima */

117 barR_y = 30;

118 else if ( barR_y > 380 ) /* se a raquete direita estiver saindo por baixo */

119 barR_y = 380;

120 } /* fim da função respondToKeyboard */

121
122 void reverseVerticalDirection() /* inverte a direção acima-abaixo da bola */

123 {

124 if ( ( direction % 2 ) == 0 ) /* direções "para baixo" são números pares */


125 ++direction; /* faz a bola começar a subir */

126 else /* direções "para cima" são números ímpares */

127 --direction; /* faz a bola começar a descer */

128 play_sample( boing, 255, 128, 1000, 0 ); /* toca som "boing" uma vez */

129 } /* fim da função reverseVerticalDirection */

130
131 void reverseHorizontalDirection() /* inverte a direção horizontal */

132 {

133 direction = ( direction + 2 ) % 4; /* inverte a direção horizontal */

134 play_sample( boing, 255, 128, 1000, 0 ); /* toca som "boing" uma vez */

135 } /* fim da função reverseHorizontalDirection */

[F]Figura E.10 Acrescentando raquetes e entrada do teclado.


Você pode notar que não registramos as coordenadas x das raquetes neste programa.
Como as raquetes não podem se mover horizontalmente, isso não é necessário. Cada
raquete só tem uma coordenada x por toda a duração do jogo, de modo que não precisamos
de uma variável que registra esses valores. Além disso, assim como a bola, a coordenada y
máxima permitida para as raquetes não é 480 (a altura da tela), mas sim 380. Isso porque as
raquetes possuem 100 pixels de altura e uma coordenada y de 380 significa que a parte
inferior da raquete está na coordenada y 480 a parte inferior da tela.
Outra coisa a observar é que, embora carreguemos o bitmap bar apenas uma vez,
pudemos desenhá-lo na tela em dois lugares diferentes simultaneamente. O Allegro permite
que o mesmo bitmap seja desenhado em muitos lugares diferentes, um recurso que é útil se
você precisar de dois bitmaps idênticos na tela ao mesmo tempo.
Porém, neste ponto, embora as raquetes possam ser movidas com o teclado, elas não
têm qualquer efeito sobre a bola ela continuará quicando pela tela, independente de as
raquetes estarem ou não em seu caminho. Lidaremos com esse problema na Seção E.9,
quando codificarmos esse recurso em nosso jogo. Por enquanto, examinaremos outra
capacidade do Allegro exibir texto na tela.

E.8 Fontes e exibição de texto


Em quase todos os jogos, até mesmo nos mais simples, é necessário que o jogo se
comunique com o usuário de alguma maneira. Isso pode variar desde dar instruções ao
usuário enquanto o jogo está em execução até simplesmente dizer ao usuário quantos pontos
ele ou ela obteve até o momento. Para fazer isso, o desenvolvedor do jogo precisa exibir
texto na tela, para que o jogador possa lê-lo. No Allegro, exibir texto é algo tratado de modo
semelhante a exibir bitmaps e tocar sons.
Para exibir texto no Allegro, a coisa mais importante que precisamos especificar
além do texto real a ser exibido, naturalmente é a fonte em que isso deverá ser exibido.
Assim como bitmaps e sons, o Allegro pode carregar fontes de arquivos externos, e ele
define um tipo que aponta para o local na memória onde esses arquivos estão armazenados
FONT*. Carregar fontes é algo feito com a função load_font, que funciona da mesma forma
que load_bitmap e load_sample. De modo semelhante, quando você terminar de usar uma fonte,
ela deverá ser destruída com a função destroy_font, para impedir perdas de memória. Se você
quiser carregar uma fonte de um arquivo, ele deverá estar no formato .fnt, .bmp ou .pcx. Os
parâmetros de load_font são ligeiramente diferentes daqueles de load_bitmap e load_sample, de modo
que consideramos seu protótipo:
FONT *load_font( const char *filename, RGB *palette, void *parameter );

O primeiro parâmetro, obviamente, é o nome do arquivo de fonte que está sendo carregado.
O segundo parâmetro é algo que já vimos uma palheta. Porém, assim como bitmaps, se a
profundidade de cor não for 8 bits, não temos realmente de passar uma palheta para a
função. Ela pode ser seguramente NULL, sem qualquer consequência. Não usaremos o
terceiro parâmetro ele é usado para dizer ao Allegro para carregar fontes de diferentes
maneiras, o que não precisamos fazer. Assim como o segundo parâmetro, ele pode ser
definido como NULL sem causar quaisquer problemas. Quando uma fonte tiver sido
carregada, você poderá usar as funções na Figura E.11 para desenhar texto em um bitmap
ou na tela.
Protótipo de função Descrição
void textprintf_ex(BITMAP *bmp, Desenha a string de controle de formato
const FONT *f, int x, int y, especificada por fmt e os parâmetros
seguintes no bmp nas coordenadas
int color, int bgColor,
especificadas. O texto é desenhado na fonte
const char *fmt, ...)
e em cores especificadas, e fica alinhado à
esquerda.

void textprintf_centre_ex( Funciona da mesma maneira que textprintf_ex,


BITMAP *bmp, const FONT *f, mas o texto desenhado é centralizado nas
coordenadas especificadas.
int x, int y, int color,

int bgColor, const char *fmt,

...)

void textprintf_right_ex( Funciona da mesma maneira que textprintf_ex,


BITMAP *bmp, const FONT *f, mas o texto desenhado é alinhado à direita
nas coordenadas especificadas.
int x, int y, int color,

int bgColor, const char *fmt,

...)

int text_length(const FONT *f, Retorna a largura (em pixels) da string


const char *string) especificada quando desenhada na fonte
especificada. Útil para alinhar várias saídas
de texto.
int text_height(const FONT *f, Retorna a altura (em pixels) da string
const char *string) especificada quando desenhada na fonte
especificada. Útil para alinhar várias saídas
de texto.
[F]Figura E.11 Funções úteis para desenhar texto em um bitmap.
(O cabeçalho allegro.h define uma variável global, font, que contém os dados para a
fonte ―default‖ do Allegro. Essa fonte pode ser usada se você não tiver um arquivo de fonte
para carregar. Oferecemos um arquivo de fonte para nosso jogo Pong, mas este ainda é um
recurso útil para você conhecer.)
Como você pode ver, as funções de saída de texto possuem um grande número de
parâmetros. Pode ser útil dar uma olhada em um exemplo de uma chamada de função para
entender como a função funciona.
textprintf_ex( buffer, font, 0, 0, makecol( 0, 0, 0 ), -1,

"Hello!" );

Esta chamada de função mostra a string "Hello!" no canto superior esquerdo de um buffer (que
pode ser desenhada mais tarde na tela), usando a fonte default. O texto é exibido em preto,
com uma cor de fundo transparente.
A string que passamos à função textprintf_ex é uma string de controle de formato. Isso
significa que podemos usar qualquer um dos especificadores de conversão discutidos no
Capítulo 9 para imprimir ints, doubles e outras variáveis. Isso é muito útil se, por exemplo,
quisermos imprimir o escore de um jogador, como precisaremos fazer em nosso jogo Pong.
Passamos ao parâmetro bgColor um valor -1 na chamada de exemplo. O Allegro não
pode criar essa cor com uma chamada a makecol, pois ela interpreta o valor de -1 como ―sem
cor‖ ou ―transparente‖. Isso significa que o texto exibido não terá cor de fundo e que
qualquer coisa ―por trás‖ do texto será visível.
Normalmente, a fonte default do Allegro serve para a exibição de texto, mas
fornecemos o arquivo de fonte pongfont.pcx com os exemplos do apêndice, para uso com nosso
programa. Em nosso jogo de Pong, usamos as funções descritas anteriormente para exibir os
escores de cada um dos jogadores. Por esse motivo, acrescentamos os ints, scoreL e scoreR a
essa iteração do programa. Observe, porém, que como as raquetes ainda não fazem nada,
não podemos manter o escore no jogo, e portanto os valores de scoreL e scoreR permanecerão
em 0 durante o programa. Apesar disso, a Figura E.12 mostra como usar as funções
explicadas anteriormente para exibir texto na tela usando a fonte fornecida em nosso site.
1 /* Figura E.12: figE_12.c

2 Exibindo texto na tela. */

3 #include <allegro.h>

4
5 /* Constantes simbólicas para as direções de posição da bola */

6 #define DOWN_RIGHT 0

7 #define UP_RIGHT 1

8 #define DOWN_LEFT 2
9 #define UP_LEFT 3

10
11 /* protótipos de função */

12 void moveBall( void );

13 void respondToKeyboard( void );

14 void reverseVerticalDirection( void );

15 void reverseHorizontalDirection( void );

16
17 int ball_x; /* coordenada-x da bola */

18 int ball_y; /* coordenada-y da bola */

19 int barL_y; /* coordenada-y da raquete esquerda */

20 int barR_y; /* coordenada-y da raquete direita */

21 int scoreL; /* escore do jogador da esquerda */

22 int scoreR; /* escore do jogador da direita */

23 int direction; /* a direção da bola */

24 BITMAP *ball; /* ponteiro para o bitmap de imagem da bola */

25 BITMAP *bar; /* ponteiro para bitmap de imagem da raquete */

26 BITMAP *buffer; /* ponteiro para o buffer */

27 SAMPLE *boing; /* ponteiro para arquivo de som */

28 FONT *pongFont; /* ponteiro para arquivo de fonte */

29
30 int main( void )

31 {

32 /* primeiro, configura o Allegro e o modo gráfico */

33 allegro_init(); /* inicializa o Allegro */

34 install_keyboard(); /* instala o teclado para o Allegro usar */

35 install_sound( DIGI_AUTODETECT, MIDI_AUTODETECT, NULL );

36 set_color_depth( 16 ); /* define a profunidade de cor em 16 bits */

37 set_gfx_mode( GFX_AUTODETECT, 640, 480, 0, 0 ); /* define o modo gráfico */

38 ball = load_bitmap( "ball.bmp", NULL ); /* carrega o bitmap da bola */

39 bar = load_bitmap( "bar.bmp", NULL); /* carrega o bitmap bar */

40 buffer = create_bitmap( SCREEN_W, SCREEN_H );/* cria o buffer */

41 boing = load_sample( "boing.wav" ); /* carrega o arquivo de som */


42 pongFont = load_font( "pongfont.pcx", NULL, NULL ); /* carrega a fonte */

43 ball_x = SCREEN_W / 2; /* dá à bola sua coordenada-x inicial */

44 ball_y = SCREEN_H / 2; /* dá à bola sua coordenada-y inicial */

45 barL_y = SCREEN_H / 2; /* dá à raquete esquerda sua coordenada-y inicial */

46 barR_y = SCREEN_H / 2; /* dá à raquete direita sua coordenada-y inicial */

47 scoreL = 0; /* define escore do jogador da esquerda como 0 */

48 scoreR = 0; /* define escore do jogador da direita como 0 */

49 srand( time( NULL ) ); /* gera semente aleatória da função ... */

50 direction = rand() % 4; /* depois cria uma direção inicial aleatória */

51
52 while ( !key[KEY_ESC] ) /* até que a tecla Escape seja pressionada ... */

53 {

54 moveBall(); /* move a bola */

55 respondToKeyboard() /* responde à entrada do teclado */

56 /* agora, realiza o buffering duplo */

57 clear_to_color( buffer, makecol( 255, 255, 255 ) );

58 blit( ball, buffer, 0, 0, ball_x, ball_y, ball->w, ball->h );

59 blit( bar, buffer, 0, 0, 0, barL_y, bar->w, bar->h );

60 blit( bar, buffer, 0, 0, 620, barR_y, bar->w, bar->h );

61 /* desenha texto no buffer */

62 textprintf_ex( buffer, pongFont, 75, 0, makecol( 0, 0, 0 ),

63 -1, "Left Player Score: %d", scoreL );

64 textprintf_ex( buffer, pongFont, 400, 0, makecol( 0, 0, 0 ),

65 -1, "Right Player Score: %d", scoreR );

66 blit( buffer, screen, 0, 0, 0, 0, buffer->w, buffer->h );

67 clear_bitmap( buffer );

68 } /* fim do while */

69
70 destroy_bitmap( ball ); /* destrói o bitmap da bola */

71 destroy_bitmap( bar ); /* destrói o bitmat bar */

72 destroy_bitmap( buffer ); /* destrói o bitmap do buffer */

73 destroy_sample( boing ); /* destrói o arquivo de som boing */


74 destroy_font( pongFont ); /* destrói a fonte */

75 return 0;

76 } /* fim da função main */

77 END_OF_MAIN() /* não se esqueça disto! */

78
79 void moveBall() /* move a bola */

80 {

81 switch ( direction ) {

82 case DOWN_RIGHT:

83 ++ball_x; /* move a bola para a direita */

84 ++ball_y; /* move a bola para baixo */

85 break;

86 case UP_RIGHT:

87 ++ball_x; /* move a bola para a direita */

88 --ball_y; /* move a bola para cima */

89 break;

90 case DOWN_LEFT:

91 --ball_x; /* move a bola para a esquerda */

92 ++ball_y; /* move a bola para baixo */

93 break;

94 case UP_LEFT:

95 --ball_x; /* move a bola para a esquerda */

96 --ball_y; /* move a bola para cima */

97 break;

98 } /* fim do switch */

99
100 /* cuida para que a bola não saia da tela */

101
102 /* se a bola está saindo acima ou abaixo ... */

103 if ( ball_y <= 30 || ball_y >= 440 )

104 reverseVerticalDirection();

105
106 /* se a bola está saindo para a esquerda ou para a direita ... */
107 if ( ball_x <= 0 || ball_x >= 600 )

108 reverseHorizontalDirection();

109 } /* fim da função moveBall */

110
111 void respondToKeyboard() /* responde à entrada do teclado */

112 {

113 if ( key[KEY_A] ) /* se A estiver sendo pressionado... */

114 barL_y -= 3; /* ... move a raquete esquerda para cima */

115 if ( key[KEY_Z] ) /* se A estiver sendo pressionado... */

116 barL_y += 3; /* ... move a raquete esquerda para baixo */

117
118 if ( key[KEY_UP] ) /* se seta para cima estiver sendo pressionada... */

119 barR_y -= 3; /* ... move a raquete direita para cima */

120 if ( key[KEY_DOWN] ) /* se seta para baixo estiver sendo pressionada...*/

121 barR_y += 3; /* ... move a raquete direita para baixo */

122
123 /* cuida para que as raquetes não saiam da tela */

124 if ( barL_y < 30 ) /* se a raquete esquerda estiver saindo por cima */

125 barL_y = 30;

126 else if ( barL_y > 380 ) /* se a raquete esquerda estiver saindo por baixo */

127 barL_y = 380;

128 if ( barR_y < 30 ) /* se a raquete direita estiver saindo por cima */

129 barR_y = 30;

130 else if ( barR_y > 380 ) /* se a raquete direita estiver saindo por baixo */

131 barR_y = 380;

132 } /* fim da função respondToKeyboard */

133
134 void reverseVerticalDirection() /* inverte a direção acima-abaixo da bola */

135 {

136 if ( ( direction % 2 ) == 0 ) /* direções "para baixo" são números pares */

137 ++direction; /* faz a bola começar a subir */

138 else /* direções "para cima" são números ímpares */

139 --direction; /* faz a bola começar a descer */


140 play_sample( boing, 255, 128, 1000, 0 ); /* toca som "boing" uma vez */

141 } /* fim da função reverseVerticalDirection */

142
143 void reverseHorizontalDirection() /* inverte a direção horizontal */

144 {

145 direction = ( direction + 2 ) % 4; /* inverte a direção horizontal */

146 play_sample( boing, 255, 128, 1000, 0 ); /* toca som "boing" uma vez */

147 } /* fim da função reverseHorizontalDirection */

[F]Figura E.12 Exibindo texto na tela.

E.9 Implementando o jogo de Pong


Agora, temos todos os elementos do nosso jogo de Pong em nosso programa uma bola,
raquetes em movimento, sons e um placar. Porém, esses elementos ainda não são capazes
de interagir uns com os outros. Nesta seção, uniremos as pontas soltas em nosso programa
para fazê-lo funcionar como um jogo real de Pong.
O ponto fraco na versão atual do nosso programa é que as raquetes ainda não param
a bola ela continua se movendo independente de haver ou não raquetes em seu caminho.
Além disso, quando a bola atinge a borda esquerda ou direita da tela, ela simplesmente
quica, em vez de sair da tela, e o jogador não é recompensado com um ponto.
O método que usamos para resolver esses problemas é incrivelmente simples. O
Allegro não possui qualquer função que determine se dois ou mais bitmaps estão se
tocando, mas, como sabemos as dimensões dos bitmaps ball e bar, podemos facilmente testar
se a bola atingiu uma raquete. Como precisamos verificar somente se a raquete está no
caminho da bola se ela estiver saindo pelo lado esquerdo ou direito da tela, podemos fazer
essa verificação dentro da estrutura if que verifica a coordenada x da bola.
A outra questão que enfrentamos é o fato de que, embora tenhamos criado um limite
perto do topo da tela, que garante que a bola não entre no placar, não há uma indicação
visual de que o limite está lá a bola simplesmente parece quicar sem motivo. Embora
saibamos por que isso está acontecendo, pode confundir os jogadores.
Já mencionamos no apêndice que o Allegro pode desenhar gráficos simples. Além
de ser capaz de desenhar retângulos, círculos e polígonos, o Allegro tem uma função line
para desenhar uma linha de um ponto para outro. Podemos usar essa função para desenhar
uma linha onde nosso limite se encontra. O protótipo para essa função é o seguinte:
void line(BITMAP *bitmap, int x1, int y1, int x2, int y2, int color)

Essa função desenha uma linha reta no bitmap especificado a partir das coordenadas (x1, y1)
até as coordenadas (x2, y2). A linha será desenhada na cor indicada, que pode ser
especificada usando a função makecol. Agora, podemos colocar os retoques finais em nosso
jogo de Pong com o código da Figura E.13.
1 /* Figura E.13: figE_13.c

2 Terminando o programa Pong. */


3 #include <allegro.h>

4
5 /* Constantes simbólicas para as direções de posição da bola */

6 #define DOWN_RIGHT 0

7 #define UP_RIGHT 1

8 #define DOWN_LEFT 2

9 #define UP_LEFT 3

10
11 /* protótipos de função */

12 void moveBall( void );

13 void respondToKeyboard( void );

14 void reverseVerticalDirection( void );

15 void reverseHorizontalDirection( void );

16
17 int ball_x; /* coordenada-x da bola */

18 int ball_y; /* coordenada-y da bola */

19 int barL_y; /* coordenada-y da raquete esquerda */

20 int barR_y; /* coordenada-y da raquete direita */

21 int scoreL; /* escore do jogador da esquerda */

22 int scoreR; /* escore do jogador da direita */

23 int direction; /* a direção da bola */

24 BITMAP *ball; /* ponteiro para o bitmap de imagem da bola */

25 BITMAP *bar; /* ponteiro para bitmap de imagem da raquete */

26 BITMAP *buffer; /* ponteiro para o buffer */

27 SAMPLE *boing; /* ponteiro para arquivo de som */

28 FONT *pongFont; /* ponteiro para arquivo de fonte */

29
30 int main( void )

31 {

32 /* primeiro, configura o Allegro e o modo gráfico */

33 allegro_init(); /* inicializa o Allegro */

34 install_keyboard(); /* instala o teclado para o Allegro usar */

35 install_sound( DIGI_AUTODETECT, MIDI_AUTODETECT, NULL );


36 set_color_depth( 16 ); /* define a profunidade de cor em 16 bits */

37 set_gfx_mode( GFX_AUTODETECT, 640, 480, 0, 0 ); /* define o modo gráfico */

38 ball = load_bitmap( "ball.bmp", NULL ); /* carrega o bitmap da bola */

39 bar = load_bitmap( "bar.bmp", NULL); /* carrega o bitmap bar */

40 buffer = create_bitmap( SCREEN_W, SCREEN_H );/* cria o buffer */

41 boing = load_sample( "boing.wav" ); /* carrega o arquivo de som */

42 pongFont = load_font( "pongfont.pcx", NULL, NULL ); /* carrega a fonte */

43 ball_x = SCREEN_W / 2; /* dá à bola sua coordenada-x inicial */

44 ball_y = SCREEN_H / 2; /* dá à bola sua coordenada-y inicial */

45 barL_y = SCREEN_H / 2; /* dá à raquete esquerda sua coordenada-y inicial */

46 barR_y = SCREEN_H / 2; /* dá à raquete direita sua coordenada-y inicial */

47 scoreL = 0; /* define escore do jogador da esquerda como 0 */

48 scoreR = 0; /* define escore do jogador da direita como 0 */

49 srand( time( NULL ) ); /* gera semente aleatória da função ... */

50 direction = rand() % 4; /* depois cria uma direção inicial aleatória */

51
52 while ( !key[KEY_ESC] ) /* até que a tecla Escape seja pressionada ... */

53 {

54 moveBall(); /* move a bola */

55 respondToKeyboard() /* responde à entrada do teclado */

56 /* agora, realiza o buffering duplo */

57 clear_to_color( buffer, makecol( 255, 255, 255 ) );

58 blit( ball, buffer, 0, 0, ball_x, ball_y, ball->w, ball->h );

59 blit( bar, buffer, 0, 0, 0, barL_y, bar->w, bar->h );

60 blit( bar, buffer, 0, 0, 620, barR_y, bar->w, bar->h );

61 line( buffer, 0, 30, 640, 30, makecol( 0, 0, 0 ) );

62 /* desenha texto no buffer */

63 textprintf_ex( buffer, pongFont, 75, 0, makecol( 0, 0, 0 ),

64 -1, "Left Player Score: %d", scoreL );

65 textprintf_ex( buffer, pongFont, 400, 0, makecol( 0, 0, 0 ),

66 -1, "Right Player Score: %d", scoreR );

67 blit( buffer, screen, 0, 0, 0, 0, buffer->w, buffer->h );


68 clear_bitmap( buffer );

69 } /* fim do while */

70
71 destroy_bitmap( ball ); /* destrói o bitmap da bola */

72 destroy_bitmap( bar ); /* destrói o bitmat bar */

73 destroy_bitmap( buffer ); /* destrói o bitmap do buffer */

74 destroy_sample( boing ); /* destrói o arquivo de som boing */

75 destroy_font( pongFont ); /* destrói a fonte */

76 return 0;

77 } /* fim da função main */

78 END_OF_MAIN() /* não se esqueça disto! */

79
80 void moveBall() /* move a bola */

81 {

82 switch ( direction ) {

83 case DOWN_RIGHT:

84 ++ball_x; /* move a bola para a direita */

85 ++ball_y; /* move a bola para baixo */

86 break;

87 case UP_RIGHT:

88 ++ball_x; /* move a bola para a direita */

89 --ball_y; /* move a bola para cima */

90 break;

91 case DOWN_LEFT:

92 --ball_x; /* move a bola para a esquerda */

93 ++ball_y; /* move a bola para baixo */

94 break;

95 case UP_LEFT:

96 --ball_x; /* move a bola para a esquerda */

97 --ball_y; /* move a bola para cima */

98 break;

99 } /* fim do switch */

100
101 /* se a bola está saindo acima ou abaixo ... */

102 if ( ball_y <= 30 || ball_y >= 440 )

103 reverseVerticalDirection();

104
105 /* se a bola estiver no alcance da raquele esquerda ... */

106 if (ball_x < 20 && (direction == DOWN_LEFT || direction == UP_LEFT))

107 {

108 /* a raquete esquerda está no caminho? */

109 if ( ball_y > ( barL_y - 39 ) && ball_y < ( barL_y + 99 ) )

110 reverseHorizontalDirection();

111 else if ( ball_x <= -20 ) { /* se a bola sair da tela */

112 ++scoreR; /* dá um ponto ao jogador da direita */

113 ball_x = SCREEN_W / 2; /* coloca a bola no ... */

114 ball_y = SCREEN_H / 2; /* ... centro da tela */

115 direction = rand() % 4; /* dá à bola uma direção aleatória */

116 } /* fim do else */

117 } /* fim do if */

118
119 /* se a bola estiver no alcance da raquele direita ... */

120 if (ball_x > 580 && (direction == DOWN_RIGHT || direction == UP_RIGHT))

121 {

122 /* a raquete direita está no caminho? */

123 if ( ball_y > ( barR_y - 39 ) && ball_y < ( barR_y + 99 ) )

124 reverseHorizontalDirection();

125 else if ( ball_x >= 620 ) { /* se a bola sair da tela */

126 ++scoreL; /* dá um ponto ao jogador da esquerda */

127 ball_x = SCREEN_W / 2; /* coloca a bola no ... */

128 ball_y = SCREEN_H / 2; /* ... centro da tela */

129 direction = rand() % 4; /* dá à bola uma direção aleatória */

130 } /* fim do else */

131 } /* fim do if */

132 } /* fim da função moveBall */

133
134 void respondToKeyboard() /* responde à entrada do teclado */

121 {

122 if ( key[KEY_A] ) /* se A estiver sendo pressionado... */

123 barL_y -= 3; /* ... move a raquete esquerda para cima */

124 if ( key[KEY_Z] ) /* se A estiver sendo pressionado... */

125 barL_y += 3; /* ... move a raquete esquerda para baixo */

126
127 if ( key[KEY_UP] ) /* se seta para cima estiver sendo pressionada... */

128 barR_y -= 3; /* ... move a raquete direita para cima */

129 if ( key[KEY_DOWN] ) /* se seta para baixo estiver sendo pressionada...*/

130 barR_y += 3; /* ... move a raquete direita para baixo */

131
132 /* cuida para que as raquetes não saiam da tela */

133 if ( barL_y < 30 ) /* se a raquete esquerda estiver saindo por cima */

134 barL_y = 30;

135 else if ( barL_y > 380 ) /* se a raquete esquerda estiver saindo por baixo */

136 barL_y = 380;

137 if ( barR_y < 30 ) /* se a raquete direita estiver saindo por cima */

138 barR_y = 30;

139 else if ( barR_y > 380 ) /* se a raquete direita estiver saindo por baixo */

140 barR_y = 380;

141 } /* fim da função respondToKeyboard */

156
157 void reverseVerticalDirection() /* inverte a direção acima-abaixo da bola */

158 {

159 if ( ( direction % 2 ) == 0 ) /* direções "para baixo" são números pares */

160 ++direction; /* faz a bola começar a subir */

161 else /* direções "para cima" são números ímpares */

162 --direction; /* faz a bola começar a descer */

163 play_sample( boing, 255, 128, 1000, 0 ); /* toca som "boing" uma vez */

164 } /* fim da função reverseVerticalDirection */

165
166 void reverseHorizontalDirection() /* inverte a direção horizontal */
167 {

168 direction = ( direction + 2 ) % 4; /* inverte a direção horizontal */

169 play_sample( boing, 255, 128, 1000, 0 ); /* toca som "boing" uma vez */

170 } /* fim da função reverseHorizontalDirection */

[F]Figura E.13 Terminando o jogo de Pong.


Você poderá se perguntar por que escolhemos tais limites para verificar se a raquete
está tocando na bola. Para que a bola ―quique‖ em uma raquete, pelo menos 1 pixel da bola
precisa estar tocando na (ou seja, imediatamente adjacente à) raquete. Assim, se ball_y for 39
a menos que barL_y, o pixel mais baixo da bola está tocando na barra, e se ball_y for 99 a mais
que barL_y, o pixel mais alto está tocando.
Com esta versão do programa, nosso jogo de Pong agora está totalmente funcional,
mas ainda existem algumas melhorias que podemos fazer. A primeira delas é usar timers
para regular a velocidade do jogo, um assunto que discutiremos na próxima seção.

E.10 Timers no Allegro


Um recurso que falta em nosso jogo de Pong é a capacidade de regular a velocidade do
jogo. No momento, o processo principal do nosso jogo na Figura E.13 está contido em um
loop while. Porém, essa não é uma boa prática, pois a velocidade em que um programa é
executado varia de um sistema para outro com base em fatores como a velocidade do
processador. Dessa forma, nosso loop while poderia operar em velocidades variáveis,
dependendo do sistema em que estivermos jogando, fazendo nossa bola e as raquetes se
moverem mais rapidamente ou mais lentamente do que queremos. Nesta seção,
apresentamos os timers do Allegro, para ajudar a regular a velocidade do nosso jogo.
Instalamos o manipulador do timer para o Allegro usar chamando a função install_timer,
que não usa parâmetros. Quando tivermos chamado a função install_timer, poderemos
acrescentar timers em nosso programa. Isso é feito chamando a função install_int. O protótipo
para essa função é:
int install_int( void ( *function )(), int interval );

Você notará que o parâmetro function é um ponteiro de função. Chamar a função install_int
acrescenta um timer ao seu programa, que chama a função especificada por function a cada
interval milissegundos. Assim, se quiséssemos chamar uma função timedFunction uma vez a cada
segundo, incluiríamos o seguinte código:
install_int( timedFunction, 1000 );

Não é preciso armazenar um timer recém-instalado em uma variável o timer começará a


ser executado automaticamente em segundo plano no programa e permanecerá em execução
até que seja removido ou o programa termine. Além disso, o Allegro só identifica um timer
pela função que ele foi preparado para chamar. Chamar install_timer e passar-lhe uma função
que já tem um timer ligado a ela não cria um novo timer; isso simplesmente muda a
velocidade do timer antigo para aquela especificada pelo parâmetro interval. De modo
semelhante, remover um timer é algo feito chamando a função remove_timer e passando-lhe o
nome da função à qual o timer está ligado.
O Allegro permite apenas 16 timers rodando ao mesmo tempo, independente do
sistema usado. Se tentarmos incluir mais, a função install_int falha e retorna um valor diferente
de 0. Em geral, porém, você deve evitar o uso de um número excessivo de timers, pois
alguns processos Allegro também exigem que os timers funcionem, e eles ocupam os
mesmos slots como timers normais. Nenhuma das funções do Allegro que discutimos até
aqui exige timers, mas algumas das funções discutidas na Seção E.12 especificamente, as
funções que reproduzem animações .fli e arquivos de música MIDI precisam deles.
A etapa final para tudo funcionar corretamente é acrescentar o qualificador volatile
discutido no Capítulo 14 a qualquer variável cujo valor possa ser mudado por nossos timers.
Como o Allegro é uma biblioteca externa, os compiladores não podem reconhecer o que a
função install_int faz. Por esse motivo, o compilador pode não ―entender‖ que uma variável
pode ser modificada por um timer, e pode tentar otimizar o código principal do programa
carregando o valor da variável em um dos registradores do computador, deixando assim de
observar quando esse valor muda. A inclusão do qualificador volatile a uma variável que pode
ser modificada por um timer avisa ao compilador que esse valor pode mudar
inesperadamente, de modo que precisa gerar código que recarrega corretamente o valor
mais recente da memória.
Agora, podemos incluir timers ao nosso jogo de Pong para regular a rapidez com
que a bola e as raquetes se movem na tela (Figura E.14).
1 /* Figura E.14: figE_14.c

2 Terminando o programa Pong. */

3 #include <allegro.h>

4
5 /* Constantes simbólicas para as direções de posição da bola */

6 #define DOWN_RIGHT 0

7 #define UP_RIGHT 1

8 #define DOWN_LEFT 2

9 #define UP_LEFT 3

10
11 /* protótipos de função */

12 void moveBall( void );

13 void respondToKeyboard( void );

14 void reverseVerticalDirection( void );

15 void reverseHorizontalDirection( void );

16
17 volatile int ball_x; /* coordenada-x da bola */

18 volatile int ball_y; /* coordenada-y da bola */

19 volatile int barL_y; /* coordenada-y da raquete esquerda */


20 volatile int barR_y; /* coordenada-y da raquete direita */

21 volatile int scoreL; /* escore do jogador da esquerda */

22 volatile int scoreR; /* escore do jogador da direita */

23 volatile int direction; /* a direção da bola */

24 BITMAP *ball; /* ponteiro para o bitmap de imagem da bola */

25 BITMAP *bar; /* ponteiro para bitmap de imagem da raquete */

26 BITMAP *buffer; /* ponteiro para o buffer */

27 SAMPLE *boing; /* ponteiro para arquivo de som */

28 FONT *pongFont; /* ponteiro para arquivo de fonte */

29
30 int main( void )

31 {

32 /* primeiro, configura o Allegro e o modo gráfico */

33 allegro_init(); /* inicializa o Allegro */

34 install_keyboard(); /* instala o teclado para o Allegro usar */

35 install_sound( DIGI_AUTODETECT, MIDI_AUTODETECT, NULL );

36 install_timer(); /* instala o manipulador do timer */

37 set_color_depth( 16 ); /* define a profunidade de cor em 16 bits */

38 set_gfx_mode( GFX_AUTODETECT, 640, 480, 0, 0 ); /* define o modo gráfico */

39 ball = load_bitmap( "ball.bmp", NULL ); /* carrega o bitmap da bola */

40 bar = load_bitmap( "bar.bmp", NULL); /* carrega o bitmap bar */

41 buffer = create_bitmap( SCREEN_W, SCREEN_H );/* cria o buffer */

42 boing = load_sample( "boing.wav" ); /* carrega o arquivo de som */

43 pongFont = load_font( "pongfont.pcx", NULL, NULL ); /* carrega a fonte */

44 ball_x = SCREEN_W / 2; /* dá à bola sua coordenada-x inicial */

45 ball_y = SCREEN_H / 2; /* dá à bola sua coordenada-y inicial */

46 barL_y = SCREEN_H / 2; /* dá à raquete esquerda sua coordenada-y inicial */

47 barR_y = SCREEN_H / 2; /* dá à raquete direita sua coordenada-y inicial */

48 scoreL = 0; /* define escore do jogador da esquerda como 0 */

49 scoreR = 0; /* define escore do jogador da direita como 0 */

50 srand( time( NULL ) ); /* gera semente aleatória da função ... */

51 direction = rand() % 4; /* depois cria uma direção inicial aleatória */


52 /* inclui timer que chama moveBall a cada 5 ms */

53 install_int( moveBall, 5 );

54 /* inclui timer que chama respondToKeyboard a cada 10 ms */

55 install_int( respondToKeyboard, 10 );

56
57 while ( !key[KEY_ESC] ) /* até que a tecla Escape seja pressionada ... */

58 {

59 /* agora, realiza o buffering duplo */

60 clear_to_color( buffer, makecol( 255, 255, 255 ) );

61 blit( ball, buffer, 0, 0, ball_x, ball_y, ball->w, ball->h );

62 blit( bar, buffer, 0, 0, 0, barL_y, bar->w, bar->h );

63 blit( bar, buffer, 0, 0, 620, barR_y, bar->w, bar->h );

64 line( buffer, 0, 30, 640, 30, makecol( 0, 0, 0 ) );

65 /* desenha texto no buffer */

66 textprintf_ex( buffer, pongFont, 75, 0, makecol( 0, 0, 0 ),

67 -1, "Left Player Score: %d", scoreL );

68 textprintf_ex( buffer, pongFont, 400, 0, makecol( 0, 0, 0 ),

69 -1, "Right Player Score: %d", scoreR );

70 blit( buffer, screen, 0, 0, 0, 0, buffer->w, buffer->h );

71 clear_bitmap( buffer );

72 } /* fim do while */

73
74 remove_int( moveBall ); /* remove timer moveBall */

75 remove_int( respondToKeyboard ); /* remove timer respondToKeyboard */

76 destroy_bitmap( ball ); /* destrói o bitmap da bola */

77 destroy_bitmap( bar ); /* destrói o bitmat bar */

78 destroy_bitmap( buffer ); /* destrói o bitmap do buffer */

79 destroy_sample( boing ); /* destrói o arquivo de som boing */

80 destroy_font( pongFont ); /* destrói a fonte */

81 return 0;

82 } /* fim da função main */

83 END_OF_MAIN() /* não se esqueça disto! */

84
85 void moveBall() /* move a bola */

86 {

87 switch ( direction ) {

88 case DOWN_RIGHT:

89 ++ball_x; /* move a bola para a direita */

90 ++ball_y; /* move a bola para baixo */

91 break;

92 case UP_RIGHT:

93 ++ball_x; /* move a bola para a direita */

94 --ball_y; /* move a bola para cima */

95 break;

96 case DOWN_LEFT:

97 --ball_x; /* move a bola para a esquerda */

98 ++ball_y; /* move a bola para baixo */

99 break;

100 case UP_LEFT:

101 --ball_x; /* move a bola para a esquerda */

102 --ball_y; /* move a bola para cima */

103 break;

104 } /* fim do switch */

105
106 /* se a bola está saindo acima ou abaixo ... */

107 if ( ball_y <= 30 || ball_y >= 440 )

108 reverseVerticalDirection();

109
110 /* se a bola estiver no alcance da raquele esquerda ... */

111 if (ball_x < 20 && (direction == DOWN_LEFT || direction == UP_LEFT))

112 {

113 /* a raquete esquerda está no caminho? */

114 if ( ball_y > ( barL_y - 39 ) && ball_y < ( barL_y + 99 ) )

115 reverseHorizontalDirection();

116 else if ( ball_x <= -20 ) { /* se a bola sair da tela */


117 ++scoreR; /* dá um ponto ao jogador da direita */

118 ball_x = SCREEN_W / 2; /* coloca a bola no ... */

119 ball_y = SCREEN_H / 2; /* ... centro da tela */

120 direction = rand() % 4; /* dá à bola uma direção aleatória */

121 } /* fim do else */

122 } /* fim do if */

123
124 /* se a bola estiver no alcance da raquele direita ... */

125 if (ball_x > 580 && (direction == DOWN_RIGHT || direction == UP_RIGHT))

126 {

127 /* a raquete direita está no caminho? */

128 if ( ball_y > ( barR_y - 39 ) && ball_y < ( barR_y + 99 ) )

129 reverseHorizontalDirection();

130 else if ( ball_x >= 620 ) { /* se a bola sair da tela */

131 ++scoreL; /* dá um ponto ao jogador da esquerda */

132 ball_x = SCREEN_W / 2; /* coloca a bola no ... */

133 ball_y = SCREEN_H / 2; /* ... centro da tela */

134 direction = rand() % 4; /* dá à bola uma direção aleatória */

135 } /* fim do else */

136 } /* fim do if */

137 } /* fim da função moveBall */

138
139 void respondToKeyboard() /* responde à entrada do teclado */

132 {

133 if ( key[KEY_A] ) /* se A estiver sendo pressionado... */

134 barL_y -= 3; /* ... move a raquete esquerda para cima */

135 if ( key[KEY_Z] ) /* se A estiver sendo pressionado... */

136 barL_y += 3; /* ... move a raquete esquerda para baixo */

137
138 if ( key[KEY_UP] ) /* se seta para cima estiver sendo pressionada... */

139 barR_y -= 3; /* ... move a raquete direita para cima */

140 if ( key[KEY_DOWN] ) /* se seta para baixo estiver sendo pressionada...*/

141 barR_y += 3; /* ... move a raquete direita para baixo */


142
143 /* cuida para que as raquetes não saiam da tela */

144 if ( barL_y < 30 ) /* se a raquete esquerda estiver saindo por cima */

145 barL_y = 30;

146 else if ( barL_y > 380 ) /* se a raquete esquerda estiver saindo por baixo */

147 barL_y = 380;

148 if ( barR_y < 30 ) /* se a raquete direita estiver saindo por cima */

149 barR_y = 30;

150 else if ( barR_y > 380 ) /* se a raquete direita estiver saindo por baixo */

151 barR_y = 380;

152 } /* fim da função respondToKeyboard */

153
154 void reverseVerticalDirection() /* inverte a direção acima-abaixo da bola */

155 {

156 if ( ( direction % 2 ) == 0 ) /* direções "para baixo" são números pares */

157 ++direction; /* faz a bola começar a subir */

158 else /* direções "para cima" são números ímpares */

159 --direction; /* faz a bola começar a descer */

160 play_sample( boing, 255, 128, 1000, 0 ); /* toca som "boing" uma vez */

161 } /* fim da função reverseVerticalDirection */

162
163 void reverseHorizontalDirection() /* inverte a direção horizontal */

164 {

165 direction = ( direction + 2 ) % 4; /* inverte a direção horizontal */

166 play_sample( boing, 255, 128, 1000, 0 ); /* toca som "boing" uma vez */

167 } /* fim da função reverseHorizontalDirection */

[F]Figura E.14 Incluindo timers ao jogo de Pong.


As chamadas a moveBall e respondToKeyboard foram removidas do loop while em main desta
versão do programa.
Como você pode ver, os timers são simples de implementar. Com o código na
Figura E.14, a função moveBall é chamada apenas uma vez a cada 5 ms, ou 200 vezes por
segundo, o que significa que nossa bola se moverá por esse número de pixels por segundo.
De modo semelhante, o programa chama respondToKeyboard a cada 10 ms, ou 100 vezes por
segundo, o que significa (pois a função respondToKeyboard move as raquetes em intervalos de 3
pixels) que as raquetes podem se mover por 300 pixels em um segundo. Porém, embora
estas sejam as velocidades que sugerimos para esses dois timers, fique à vontade para
alterá-las de modo que o jogo seja executado na velocidade que você deseja. Experimentar é
o melhor modo de determinar uma velocidade ideal para o jogo.

E.11 O Grabber e datafiles do Allegro


Como a maioria dos gráficos e sons do Allegro vem de arquivos externos, é preciso que
cada programa carregue os arquivos de que precisa e os destrua quando o programa
terminar, para evitar perdas de memória. Quando temos um pequeno número de arquivos
externos, como em nosso jogo de Pong, carregar e destruir cada arquivo não são tarefas
difíceis. Porém, e se tivéssemos um grande número de arquivos a serem carregados? Não
apenas seria difícil lembrar de cada arquivo que teríamos de carregar e destruir, mas
também teríamos de distribuir cada um desses arquivos externos com nosso jogo, se ele
fosse lançado ao público.
Felizmente, os projetistas do Allegro previram esse problema e, para resolvê-lo,
criaram o datafile. Um datafile é um único arquivo externo, criado pelo Allegro, que
mantém os dados de vários arquivos externos em um só lugar. Um programa pode carregar
um datafile e instantaneamente ter acesso a todos os arquivos sejam eles bitmaps, sons,
fontes ou qualquer outra coisa que o datafile contenha.
O diretório do Allegro contém uma ferramenta, conhecida como grabber, que
podemos usar para importar arquivos externos e criar datafiles. Nesta seção, usamos o
grabber para criar um datafile a partir dos bitmaps e sons que usamos em nosso jogo Pong,
depois usamos esse datafile para reduzir o número de linhas em nosso programa.
Para criar um datafile, temos primeiro de abrir o grabber. No Windows, ele é o
programa grabber.exe no diretório tools do arquivo zip de ferramentas e exemplos. Em outros
sistemas, você poderá simplesmente executar ―grabber‖ pela linha de comando. Execute o
grabber e a tela da Figura E.15 aparecerá.
[F]Figura E.15 Utilitário grabber do Allegro.
Existem quatro áreas principais na janela do programa grabber. A área superior, que
contém vários arquivos de texto, é o espaço que mostra as propriedades do datafile que
atualmente estão sendo modificadas. A caixa branca no lado esquerdo da janela lista os
objetos que compõem o datafile sendo editado; como ainda não criamos quaisquer objetos,
a caixa atualmente está vazia. Quando importarmos imagens e sons para o nosso datafile, o
lado direito da tela será usado para exibir as propriedades de um objeto selecionado.
Embora existam muitos itens nos menus do grabber, só precisamos de alguns deles
para criar um datafile. Para acrescentar qualquer objeto a um datafile, temos primeiro de
dizer ao grabber para criar um novo espaço no datafile para o objeto indicado. Fazemos isso
selecionando New no menu Object Read Bitmap. Aparecerá uma lista de objetos. A primeira
coisa que importamos é nosso bitmap da bola, portanto selecione Bitmap e uma caixa de
diálogo aparecerá perguntando pelo nome do objeto. Chame-o de BALL (logo você verá o
motivo para usarmos letras maiúsculas). Sua tela deverá estar como a da Figura E.16.
[F]Figura E.16 Acrescentando um bitmap a um datafile.
BALL foi acrescentado à lista de objetos no lado esquerdo da janela e o lado direito da
janela agora contém informações sobre o objeto. Porém, nosso objeto ainda não possui
dados de imagem. Para importar dados de um arquivo de bitmap, temos de primeiro
selecionar pelo menu File. O programa pergunta onde o arquivo de bitmap está localizado;
portanto, navegue até a sua pasta de projeto do Pong e importe nossa imagem ball.bmp.
Quando terminar, a imagem aparecerá na janela para confirmar que foi carregada
corretamente.
Clique em qualquer lugar na janela e a tela da Figura E.16 reaparece. O item de
menu Read Bitmap não deu realmente ao nosso objeto BALL qualquer dado de imagem ele
simplesmente carregou a imagem na memória interna do grabber. Para realmente aplicar os
dados de bitmap ao nosso objeto, cuide para que o objeto BALL esteja selecionado no lado
esquerdo da janela, depois selecione Grab no menu Object. A tela da Figura E.17 aparecerá.
[F]Figura E.17 Aplicando um bitmap importado a um objeto.
O programa agora nos pergunta qual parte do bitmap queremos que o objeto
contenha. Esse recurso é útil se quisermos que nosso objeto contenha apenas parte de uma
imagem, mas no momento queremos o bitmap inteiro da bola. Mova o cursor para o canto
superior esquerdo da imagem, depois clique e arraste uma caixa sobre o bitmap inteiro. A
tela da Figura E.18 deverá aparecer.
[F]Figura E.18 Um objeto importado completo.
Feito isto, terminamos de importar nossa imagem da bola no datafile. Crie um novo
bitmap no grabber, chamado BAR e repita o processo com o bitmap bar.bmp para importar a
imagem da raquete também. Os objetos BALL e BAR agora devem estar na lista de itens no
lado esquerdo da janela.
Agora que importamos nossos bitmaps, também precisamos importar nossos
arquivos de som e fonte. Primeiro, use o menu Object para criar um novo objeto do tipo
Sample, chamado BOING. Depois, basta selecionar Grab no menu Object. O programa pede o
local do arquivo; portanto, localize nosso arquivo boing.wav e dê um clique duplo nele. O
grabber, então, importa o arquivo de som e aplica seus dados ao objeto boing.
Usamos o mesmo processo para criar nosso objeto de fonte criamos uma nova
fonte, chamada PONGFONT, e depois usamos Grab para importar o arquivo pongfont.pcx. Quando
você tiver importado todos os objetos, sua tela deverá se parecer com a Figura E.19.
[F]Figura E.19 A janela do grabber depois de importar todos os nossos objetos.
Agora que importamos todos os nossos objetos no grabber, ainda há um último
passo que devemos realizar antes de podermos usar o datafile de modo eficaz. Você notará
que ainda não fizemos nada com os campos de texto no alto da janela do grabber. Porém, o
campo Header é de interesse para nós. Preencher esse campo com um nome de arquivo fará
com que o grabber salve um arquivo de cabeçalho junto com o datafile que estamos criando
atualmente. Esse arquivo de cabeçalho pode ser usado para facilitar o acesso a objetos
individuais do datafile em um programa. Discutiremos o arquivo de cabeçalho com mais
detalhes em breve, mas agora basta digitar pong.h no campo de texto. Depois, selecione Save
no menu File e salve o datafile como pongdatafile.dat na pasta onde seu projeto Pong está
localizado.
Agora, enfrentamos a tarefa de carregar o datafile para o nosso programa, o que não
é difícil. Assim como o Allegro define os tipos de ponteiro BITMAP*, SAMPLE* e FONT* para
bitmaps, sons e fontes, ele também define o tipo DATAFILE* que aponta para datafiles. De
modo semelhante, para carregar um datefile em um programa, chamamos a função
load_datafile e lhe passamos um nome de arquivo do datafile que criamos. Observe, porém, que
o Allegro não define uma função destroy_datafile. Para liberar a memória alocada a um datafile,
é preciso chamar a função unload_datafile.
Quando um datafile é carregado em um programa, o Allegro o considera como
sendo um array de objetos, com cada objeto tendo um índice específico no array. Se
tivermos uma variável DATAFILE* em nosso programa, chamada myDatafile, o acesso a um
objeto específico no datafile é feito com o código myDatafile[i].dat, onde i é o índice do objeto
no array. Normalmente, o Allegro atribui índices aos objetos em um datafile pela ordem em
que os objetos foram importados, de modo que, em nosso datafile, nosso objeto BALL tem
índice 0, nosso objeto BAR tem índice 1, e assim por diante.
É fácil lembrar dos índices dos objetos em nosso datafile, pois ele contém apenas
quatro objetos. Porém, com um datafile grande, lembrar dos índices de todo e qualquer
objeto pode ser algo difícil. Felizmente, mesmo com nosso datafile pequeno, não
precisamos ter de decorar o índice de cada objeto, pois o arquivo de cabeçalho pong.h que
salvamos cuida disso para nós. Se você abrir o arquivo de cabeçalho em sua IDE, verá o
código da Figura E.20.
1 /* Allegro datafile object indexes, produced by grabber v4.2.2, MinGW32 */

2 /* Datafile: c:\examples\appE\pongdatafile.dat */

3 /* Date: Wed Aug 10 16:59:35 2011 */

4 /* Do not hand edit! */


5
6 #define BALL 0 /* BMP */
7 #define BAR 1 /* BMP */
8 #define BOING 2 /* SAMP */

9 #define PONGFONT 3 /* FONT */

[F]Figura E.20 O arquivo de cabeçalho pong.h.


Incluindo o arquivo de cabeçalho em nosso programa, eliminamos a necessidade de
decorar os índices dos objetos em nosso datafile, pois o arquivo de cabeçalho atribui uma
constante simbólica a cada índice. A constante simbólica para determinado objeto é o nome
que demos a esse objeto no grabber quando o importamos, o que explica por que
escolhemos nomear nossos objetos em maiúsculas a convenção para constantes
simbólicas em C é dar-lhes nomes apenas com maiúsculas. O programa Pong da Figura
E.21 carrega e acessa um datafile.
1 /* Figura E.21: figE_21.c

2 Usando datafiles. */

3 #include <allegro.h>

4 #include "pong.h"

5
6 /* Constantes simbólicas para as direções de posição da bola */

7 #define DOWN_RIGHT 0

8 #define UP_RIGHT 1
9 #define DOWN_LEFT 2

10 #define UP_LEFT 3

11
12 /* protótipos de função */

13 void moveBall( void );

14 void respondToKeyboard( void );

15 void reverseVerticalDirection( void );

16 void reverseHorizontalDirection( void );

17
18 volatile int ball_x; /* coordenada-x da bola */

19 volatile int ball_y; /* coordenada-y da bola */

20 volatile int barL_y; /* coordenada-y da raquete esquerda */

21 volatile int barR_y; /* coordenada-y da raquete direita */

22 volatile int scoreL; /* escore do jogador da esquerda */

23 volatile int scoreR; /* escore do jogador da direita */

24 volatile int direction; /* a direção da bola */

25 BITMAP *buffer; /* ponteiro para o buffer */

26 DATAFILE *pongData; /* ponteiro para o datafile */

27
28 int main( void )

29 {

30 /* primeiro, configura o Allegro e o modo gráfico */

31 allegro_init(); /* inicializa o Allegro */

32 install_keyboard(); /* instala o teclado para o Allegro usar */

33 install_sound( DIGI_AUTODETECT, MIDI_AUTODETECT, NULL );

34 install_timer(); /* instala o manipulador do timer */

35 set_color_depth( 16 ); /* define a profunidade de cor em 16 bits */

36 set_gfx_mode( GFX_AUTODETECT, 640, 480, 0, 0 ); /* define o modo gráfico */

37 buffer = create_bitmap( SCREEN_W, SCREEN_H ); /* cria buffer */

38 pongData = load_datafile( "pongdatafile.dat" ); /* carrega o datafile */

39 ball_x = SCREEN_W / 2; /* dá à bola sua coordenada-x inicial */

40 ball_y = SCREEN_H / 2; /* dá à bola sua coordenada-y inicial */

41 barL_y = SCREEN_H / 2; /* dá à raquete esquerda sua coordenada-y inicial */


42 barR_y = SCREEN_H / 2; /* dá à raquete direita sua coordenada-y inicial */

43 scoreL = 0; /* define escore do jogador da esquerda como 0 */

44 scoreR = 0; /* define escore do jogador da direita como 0 */

45 srand( time( NULL ) ); /* gera semente aleatória da função ... */

46 direction = rand() % 4; /* depois cria uma direção inicial aleatória */

47 /* inclui timer que chama moveBall a cada 5 ms */

48 install_int( moveBall, 5 );

49 /* inclui timer que chama respondToKeyboard a cada 10 ms */

50 install_int( respondToKeyboard, 10 );

51
52 while ( !key[KEY_ESC] ) /* até que a tecla Escape seja pressionada ... */

53 {

54 /* agora, realiza o buffering duplo */

55 clear_to_color( buffer, makecol( 255, 255, 255 ) );

56 blit( pongData[BALL].dat, buffer, 0, 0, ball_x, ball_y, 40, 40 );

57 blit( pongData[BAR].dat, buffer, 0, 0, 0, barL_y, 20, 100 );

58 blit( pongData[BAR].dat, buffer, 0, 0, 620, barR_y, 20, 100 );

59 line( buffer, 0, 30, 640, 30, makecol( 0, 0, 0 ) );

60 /* desenha texto no buffer */

61 textprintf_ex( buffer, pongData[PGFONT].dat, 75, 0,

62 makecol( 0, 0, 0 ), -1, "Left Player Score: %d", scoreL );

63 textprintf_ex( buffer, pongData[PGFONT].dat, 400, 0,

64 makecol( 0, 0, 0 ),-1, "Right Player Score: %d", scoreR );

65 blit( buffer, screen, 0, 0, 0, 0, buffer->w, buffer->h );

66 clear_bitmap( buffer );

67 } /* fim do while */

68
69 remove_int( moveBall ); /* remove timer moveBall */

70 remove_int( respondToKeyboard ); /* remove timer respondToKeyboard */

71 destroy_bitmap( ball ); /* destrói o bitmap da bola */

72 unload_datafile( pongData ); /* descarrega o datafile */

73 return 0;
74 } /* fim da função main */

75 END_OF_MAIN() /* não se esqueça disto! */

76
77 void moveBall() /* move a bola */

78 {

79 switch ( direction ) {

80 case DOWN_RIGHT:

81 ++ball_x; /* move a bola para a direita */

82 ++ball_y; /* move a bola para baixo */

83 break;

84 case UP_RIGHT:

85 ++ball_x; /* move a bola para a direita */

86 --ball_y; /* move a bola para cima */

87 break;

88 case DOWN_LEFT:

89 --ball_x; /* move a bola para a esquerda */

90 ++ball_y; /* move a bola para baixo */

91 break;

92 case UP_LEFT:

93 --ball_x; /* move a bola para a esquerda */

94 --ball_y; /* move a bola para cima */

95 break;

96 } /* fim do switch */

97
98 /* se a bola está saindo acima ou abaixo ... */

99 if ( ball_y <= 30 || ball_y >= 440 )

100 reverseVerticalDirection();

101
102 /* se a bola estiver no alcance da raquele esquerda ... */

103 if (ball_x < 20 && (direction == DOWN_LEFT || direction == UP_LEFT))

104 {

105 /* a raquete esquerda está no caminho? */

106 if ( ball_y > ( barL_y - 39 ) && ball_y < ( barL_y + 99 ) )


107 reverseHorizontalDirection();

108 else if ( ball_x <= -20 ) { /* se a bola sair da tela */

109 ++scoreR; /* dá um ponto ao jogador da direita */

110 ball_x = SCREEN_W / 2; /* coloca a bola no ... */

111 ball_y = SCREEN_H / 2; /* ... centro da tela */

112 direction = rand() % 4; /* dá à bola uma direção aleatória */

113 } /* fim do else */

114 } /* fim do if */

115
116 /* se a bola estiver no alcance da raquele direita ... */

117 if (ball_x > 580 && (direction == DOWN_RIGHT || direction == UP_RIGHT))

118 {

119 /* a raquete direita está no caminho? */

120 if ( ball_y > ( barR_y - 39 ) && ball_y < ( barR_y + 99 ) )

121 reverseHorizontalDirection();

122 else if ( ball_x >= 620 ) { /* se a bola sair da tela */

123 ++scoreL; /* dá um ponto ao jogador da esquerda */

124 ball_x = SCREEN_W / 2; /* coloca a bola no ... */

125 ball_y = SCREEN_H / 2; /* ... centro da tela */

126 direction = rand() % 4; /* dá à bola uma direção aleatória */

127 } /* fim do else */

128 } /* fim do if */

129 } /* fim da função moveBall */

130
131 void respondToKeyboard() /* responde à entrada do teclado */

132 {

133 if ( key[KEY_A] ) /* se A estiver sendo pressionado... */

134 barL_y -= 3; /* ... move a raquete esquerda para cima */

135 if ( key[KEY_Z] ) /* se A estiver sendo pressionado... */

136 barL_y += 3; /* ... move a raquete esquerda para baixo */

137
138 if ( key[KEY_UP] ) /* se seta para cima estiver sendo pressionada... */

139 barR_y -= 3; /* ... move a raquete direita para cima */


140 if ( key[KEY_DOWN] ) /* se seta para baixo estiver sendo pressionada...*/

141 barR_y += 3; /* ... move a raquete direita para baixo */

142
143 /* cuida para que as raquetes não saiam da tela */

144 if ( barL_y < 30 ) /* se a raquete esquerda estiver saindo por cima */

145 barL_y = 30;

146 else if ( barL_y > 380 ) /* se a raquete esquerda estiver saindo por baixo */

147 barL_y = 380;

148 if ( barR_y < 30 ) /* se a raquete direita estiver saindo por cima */

149 barR_y = 30;

150 else if ( barR_y > 380 ) /* se a raquete direita estiver saindo por baixo */

151 barR_y = 380;

152 } /* fim da função respondToKeyboard */

153
154 void reverseVerticalDirection() /* inverte a direção acima-abaixo da bola */

155 {

156 if ( ( direction % 2 ) == 0 ) /* direções "para baixo" são números pares */

157 ++direction; /* faz a bola começar a subir */

158 else /* direções "para cima" são números ímpares */

159 --direction; /* faz a bola começar a descer */

160 play_sample( pongData[BOING].dat, 255, 128, 1000, 0 ); /* toca som */

161 } /* fim da função reverseVerticalDirection */

162
163 void reverseHorizontalDirection() /* inverte a direção horizontal */

164 {

165 direction = ( direction + 2 ) % 4; /* inverte a direção horizontal */

166 play_sample( pongData[BOING].dat, 255, 128, 1000, 0 ); /* toca som */

167 } /* fim da função reverseHorizontalDirection */

[F]Figura E.21 Usando datafiles.


Uma desvantagem dos datafiles pode ser vista nas linhas 56-58. Quando
imprimimos na tela a bola e a raquete na tela, tínhamos de passar os dois últimos
parâmetros (a width e a height do bitmap de origem) explicitamente. O Allegro considera os
objetos carregados de um datafile como sendo do tipo void *, que não pode ser
desreferenciado. Assim, tentar passar pongData[BALL].dat->w e pongData[BALL].dat->h para esses
parâmetros é um erro de sintaxe. Ainda assim, como sabemos as dimensões exatas de
nossos bitmaps, isso não é um problema.
O uso do nosso datafile não teve efeito sobre o jogo em si – ele é executado da
mesma forma que o programa da Figura E.14. Porém, este programa requer menos linhas de
código para gerar a mesma funcionalidade. Embora sejam apenas 8 linhas neste caso, um
jogo com um número maior de objetos em um datafile provavelmente teria uma diferença
muito maior. Se você criar um jogo com muitos bitmaps, sons e outros arquivos, não deixe
de usar um datafile para reduzir o tamanho do seu programa.

E.12 Outras capacidades do Allegro


Mencionamos várias vezes neste apêndice que o Allegro é capaz de desenhar gráficos
simples; usamos funções line para desenhar uma linha em nosso jogo. O Allegro também
pode desenhar muitas outras formas, como triângulos, círculos, arcos e polígonos com um
número qualquer de lados. A lista completa dessas funções está em
www.allegro.cc/manual/api/drawing-primitives/.

O Allegro também tem um conjunto de funções dedicadas unicamente a tocar


música usando arquivos MIDI. Ele define um tipo MIDI* que aponta para arquivos MIDI e
usa as funções load_midi, play_midi, stop_midi e destroy_midi para manipulá-los. Uma lista completa
dessas funções está em www.allegro.cc/manual/api/music-routines-(midi)/.
Se você tiver quaisquer animações no formato .fli que gostaria de incluir em seu jogo,
o Allegro pode exibi-las também. O Allegro não define um tipo que aponta para animações,
mas pode desenhar uma animação diretamente em um bitmap usando a função play_fli. A
documentação para essa capacidade está em www.allegro.cc/manual/api/flic-routines/.

E.13 Allegro Resource Center


Para obter mais informações sobre o Allegro, visite a seção sobre Allegro e Programação de
jogos em C do nosso resource center da C em www.deitel.com/allegro/.

Resumo
Seção E.3 Um programa Allegro simples
• Todo programa em Allegro precisa incluir o cabeçalho allegro.h, chamar a função allegro_init e
precisa ter uma chamada para a macro END_OF_MAIN imediatamente após a chave de
fechamento da função main do programa.
• A função allegro_init inicializa a biblioteca do Allegro. Ela precisa ser chamada antes de
quaisquer outras funções do Allegro, ou então o programa não funcionará corretamente.
• A função allegro_message é usada para dar ao usuário uma mensagem quando não houver
um modo gráfico de fazer isso.
• Windows, alguns sistemas Unix e Mac OS X não podem executar programas Allegro sem
a macro END_OF_MAIN. Se ela estiver faltando, os compiladores nesses sistemas não
poderão compilar um programa em Allegro. Não deixe de incluir a macro END_OF_MAIN
para garantir compatibilidade com sistemas que a exigem.
Seção E.4 Gráficos simples: Importando mapas de bits e blitando
• A maioria dos gráficos do Allegro vem de arquivos externos. O Allegro define diversos
tipos de variável diferentes, que apontam para dados de imagem na memória.
• O tipo BITMAP* é o tipo mais básico definido pelo Allegro para apontar para dados de
imagem armazenados na memória.
• A função set_color_depth é usada para definir a profundidade de cor de um programa em
Allegro. A profundidade de cor pode ser definida como 8, 15, 16, 24 ou 32 bits. Uma
profundidade de cor mais baixa requer menos memória, porém uma profundidade de cor
mais alta resulta em um programa com melhor aparência.
• A função set_gfx_mode é usada para definir o modo gráfico de um programa em Allegro.
Além de definir como o programa é exibido (ou seja, modo de tela cheia ou em janela),
ela também define quantos pixels existem na largura e altura da tela ou janela.
• O Allegro define cinco ―drivers mágicos‖ que podem ser passados à função set_gfx_mode
para especificar se o programa deve ser executado no modo de tela cheia ou em janela
GFX_AUTODETECT, GFX_AUTODETECT_FULLSCREEN, GFX_AUTODETECT_WINDOWED, GFX_SAFE e
GFX_TEXT.

• A maioria das funções em Allegro que podem falhar retornam ints para fins de verificação
de erro. Em geral, essas funções retornarão 0 se tiverem sucesso e um valor diferente de 0
se não tiverem.
• Um programa em Allegro precisa definir a profundidade de cor e o modo gráfico de um
programa antes de tentar fazer algo mais com os gráficos.
• A função create_bitmap cria um novo bitmap em branco.
• Use a função load_bitmap para carregar uma imagem de um arquivo de bitmap externo. Se a
profundidade de cor for definida como 8 bits, você precisa passar a essa função a palheta
do bitmap, além da própria imagem.
• Se a função load_bitmap falhar, ela retorna NULL. Ela não causa um erro.
• A função blit é uma das funções mais importantes no Allegro. Ela é usada para desenhar
um bloco de um bitmap para outro bitmap.
• Uma coordenada x maior corresponde a avançar para a direita no Allegro, mas uma
coordenada y maior corresponde a descer mais, e não subir.
• O tipo BITMAP* é um ponteiro para uma struct. Essa estrutura contém dois ints, w e h, que
armazenam a largura e a altura do bitmap em pixels, respectivamente.
• Use a função destroy_bitmap para destruir um bitmap e liberar a memória alocada a ele para
impedir perdas de memória.
Seção E.5 Animação com buffering duplo
• A animação no Allegro é feita blitando um objeto na tela em diferentes locais, em
intervalos regulares.
• As constantes simbólicas SCREEN_W e SCREEN_H são reservadas pelo Allegro e
correspondem à largura e à altura da tela em pixels, respectivamente.
• Use a função clear_to_color para tornar a totalidade de um bitmap de uma certa cor. Isso é
útil para definir uma cor de fundo para um programa.
• A função makecol é usada para retornar ints que o Allegro reconhece como várias cores.
• A técnica de ―buffering duplo‖ é um método que produz animação suave. A técnica
consiste em desenhar tudo em um bitmap intermediário, conhecido como buffer, e depois
desenhar todo o buffer na tela.
Seção E.6 Importando e tocando sons
• O Allegro define o tipo SAMPLE* que aponta para os dados do arquivo de som armazenados
na memória.
• Antes que quaisquer sons possam ser tocados no Allegro, a função install_sound precisa ser
chamada.
• O Allegro define dois ―drivers mágicos‖ que devem ser passados à função install_sound de
modo que o Allegro possa determinar quais drivers de placa de som ele deve usar para
tocar os sons. Esses ―drivers mágicos‖ são DIGI_AUTODETECT e MIDI_AUTODETECT.
• A função load_sample é usada para carregar um arquivo de som externo.
• Use a função play_sample para tocar uma amostra digital e a função stop_sample para
interrompê-la.
• O parâmetro volume na função play_sample determina o volume em que a amostra deve ser
tocada. Um valor 0 torna a amostra muda, enquanto um valor 255 a toca no volume
máximo.
• O parâmetro pan na função play_sample determina a posição de pan em que a amostra deve
ser tocada. Um valor 128 toca a amostra nos dois alto-falantes por igual. Um valor
inferior a este deslocará o som para o alto-falante da esquerda, enquanto um valor maior
(até um máximo de 255) deslocará o som para o alto-falante da direita.
• O parâmetro frequency na função play_sample determina a frequência (e, portanto, o pitch) em
que a amostra deverá ser tocada. Um valor 1000 tocará a amostra em frequência normal.
Um valor 2000 a tocará no dobro da frequência normal, um valor de 500 a tocará na
metade da frequência, e assim por diante.
• Passar um valor 0 ao parâmetro loop da função play_sample fará a amostra tocar apenas uma
vez antes de parar. Passar-lhe qualquer outro valor fará com que a amostra crie um loop
contínuo.
• Use a função destroy_sample para destruir uma amostra e liberar a memória alocada a ela
para impedir perdas de memória.
Seção E.7 Entrada pelo teclado
• Você precisa chamar a função install_keyboard para permitir que o Allegro reconheça e use o
teclado.
• O Allegro define um array de ints chamado key, que contém um índice para cada tecla no
teclado. Se uma tecla não estiver sendo pressionada, seu respectivo índice no array terá 0,
enquanto se a tecla estiver sendo pressionada, o índice terá um número diferente de 0.
• O Allegro define várias constantes simbólicas que correspondem às teclas no teclado.
Essas constantes são usadas em conjunto com o array key para determinar se teclas
específicas estão sendo pressionadas. O valor armazenado em key[KEY_A] determina se a
tecla A está ou não sendo pressionada, o valor armazenado em key[KEY_SPACE] determina se
a barra de espaço está sendo pressionada, e assim por diante.
• Qualquer programa que verifique a entrada do teclado usando o array key deve fazer isso
repetidamente. Caso contrário, as teclas pressionadas podem ser perdidas.
Seção E.8 Fontes e exibição de texto
• O Allegro define o tipo FONT* que aponta para dados de fonte armazenados na memória.
• A constante simbólica font corresponde à fonte default do Allegro.
• Use a função load_font para carregar os dados de fonte dos arquivos externos. Se a
profundidade de cor for definida para 8 bits, é preciso passar a palheta da fonte a essa
função, além da própria fonte.
• A função textprintf_ex imprime texto na tela. As funções textprintf_centre_ex e textprintf_right_ex
fazem a mesma coisa, mas alinham o texto impresso em diferentes posições.
• Quando o Allegro está esperando um int que corresponde a uma cor como parâmetro,
passar um valor -1 ao parâmetro fará com que o Allegro interprete essa cor como
―transparente‖.
• Use a função destroy_font para destruir uma fonte e liberar a memória alocada a ela, para
impedir perdas de memória.
Seção E.10 Timers no Allegro
• Qualquer programa que use timers precisa chamar a função install_timer antes de tentar
acrescentar quaisquer timers.
• O Allegro pode ter até 16 timers rodando ao mesmo tempo.
• Os timers são acrescentados chamando a função install_int e removidos chamando a função
remove_int.

• Um timer chama determinada função em intervalos regulares até que ele seja removido.
Não é preciso armazenar um timer em qualquer tipo de variável.
• O Allegro identifica um timer pela função que ele está programado a chamar.
• Qualquer variável cujo valor é modificado por uma função que um timer chama precisa
receber o qualificador volatile para garantir que o programa funcione corretamente.
Seção E.11 O Grabber e datafiles do Allegro
• Um datafile do Allegro é um único arquivo externo que mantém os dados de muitos
arquivos externos em um só lugar.
• O Allegro fornece o utilitário grabber para a criação e edição de datafiles.
• O grabber pode criar arquivos de cabeçalho que simplificam o acesso a objetos contidos
em um datafile.
• A função load_datafile carrega um datafile em um programa, e a função unload_datafile o
remove.
• Quando um datafile é carregado em um programa, o Allegro o considera como um array
de objetos. O índice de cada objeto no array é dependente da ordem em que os objetos
foram importados para o datafile. O primeiro objeto acrescentado ao datafile tem índice 0,
o segundo tem índice 1, e assim por diante.
• O Allegro considera os objetos carregados de um datafile como sendo do tipo void *.

Terminologia
Allegro II
blit VII
datafile XLIII
grabber XLII, XLIII, XLVI
set_gfx_mode VI

tela virtual VII


timers no Allegro XXXVII
tipo BITMAP* IV, VIII
tipo DATAFILE* XLVII
tipo FONT* XXVI
tipo SAMPLE XVI

Exercícios de autorrevisão
E.1 Preencha os espaços em cada uma das seguintes sentenças:
a) Todo programa em Allegro precisa incluir o cabeçalho ______.
b) A função ______ precisa ser chamada antes de qualquer outra função no Allegro.
c) A inclusão da macro ______ garante compatibilidade com os sistemas que a
exigem.
d) Antes que o Allegro possa exibir quaisquer gráficos, um programa precisa chamar
as funções ______ e ______.
e) A função ______ é usada para desenhar um bloco de um bitmap em outro.
f) O principal tipo definido pelo Allegro para apontar para dados do arquivo de som
é o tipo ______.
g) O Allegro define a constante simbólica ______ que corresponde à fonte default do
Allegro.
h) A função ______ é usada para retornar um inteiro que o Allegro interpreta como
uma cor.
i) O Allegro pode ter até ______ timers em execução ao mesmo tempo.
j) A função ______ é usada para acrescentar um timer a um programa.
k) O utilitário ______ é usado para criar e editar datafiles do Allegro.
E.2 Indique se cada uma das seguintes sentenças é verdadeira ou falsa. Se for falsa,
explique o motivo.
a) O bitmap screen é o único bitmap visível ao usuário.
b) As coordenadas (0, 0) referem-se ao canto inferior esquerdo de um bitmap.
c) Se o Allegro tentar carregar um arquivo externo que não existe, será gerado um erro
em tempo de execução.
d) A técnica de buffering duplo requer dois bitmaps intermediários, ou buffers, para
que funcione corretamente.
e) Passar um valor 2 ao parâmetro loop na função play_sample fará com que o arquivo de
som toque duas vezes antes de parar.
f) A função install_keyboard precisa receber um parâmetro que dê ao Allegro a informação
de driver do teclado do sistema.
g) Um programa que desenha texto na tela precisa especificar uma fonte em que esse
texto deve ser desenhado.
h) Um programa em Allegro pode ter até 32 timers rodando ao mesmo tempo.
i) A função usada para liberar a memória que está armazenando um datafile é a função
destroy_datafile.

E.3 Escreva instruções para realizar cada um dos seguintes:


a) Definir o modo gráfico de um programa em Allegro para uma janela com 640
pixels de largura por 480 pixels de altura.
b) Desenhar o bitmap bmp no canto superior esquerdo do bitmap buffer.
c) Tocar a amostra digital sample em volume máximo, com posição de pan
centralizada e frequência normal, sem looping.
d) Se a barra de espaço estiver sendo pressionada, definir o valor do int number como 0.
e) Desenhe a string "Olá!" no canto superior esquerdo do bitmap buffer, usando a fonte
default do Allegro com uma cor de fundo azul e uma cor de fundo transparente.
f) Inclua um timer que chama a função timedFunction quatro vezes a cada segundo.
g) Carregue o datafile com o nome de arquivo datafile.dat.
E.4 Ache o erro em cada um dos seguintes:
a) BITMAP bmp;
b) set_gfx_mode( WINDOWED, 640, 480, 0, 0 );

c) makecol( 0, 0, 256 );

Respostas dos exercícios de autorrevisão


E.1 a) allegro.h. b) allegro_init. c) END_OF_MAIN. d) set_color_depth e set_gfx_mode. e) blit. f) SAMPLE*. g) font.
h) makecol. i) 16. j) install_int. k) grabber.

E.2
a) Verdadeiro.
b) Falso. As coordenadas (0, 0) referem-se ao canto superior esquerdo de um bitmap.
c) Falso. A função que retorna o ponteiro para o arquivo externo retornará NULL, mas
nenhum erro ocorrerá nessa linha.
d) Falso. O buffering duplo requer apenas um bitmap intermediário.
e) Falso. Passar qualquer valor diferente de 0 ao parâmetro loop fará com que o arquivo
de som seja executado em loop contínuo.
f) Falso. A função install_keyboard não usa parâmetro algum.
g) Verdadeiro.
h) Falso. Um programa em Allegro só pode ter 16 timers em execução ao mesmo
tempo.
i) Falso. Essa função é a função unload_datafile. A função destroy_datafile não é definida pelo
Allegro.
E.3
a) set_gfx_mode( GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0 );

b) blit( bmp, buffer, 0, 0, 0, 0, bmp->w, bmp->h );

c) play_sample( sample, 255, 128, 1000, 0 );

d) if ( key[KEY_SPACE] ) number = 0;

e) textprintf_ex( buffer, font, 0, 0, makecol( 0, 0, 255 ), -1, "Olá!" );

f) install_int( timedFunction, 250 );

g) load_datafile( "datafile.dat" );

E.4
a) A variável bmp deveria ser declarada como um ponteiro para um BITMAP. Todas as
funções de bitmap do Allegro ou usam um ponteiro como parâmetro ou retornam
um ponteiro. Um BITMAP que não é um ponteiro é basicamente inútil.
b) O Allegro não define um ―driver mágico‖ WINDOWED. Use o ―driver mágico‖
GFX_AUTODETECT_WINDOWED em seu lugar.

c) A função makecol só pode aceitar parâmetros com valores entre 0 e 255.

Exercícios
E.5 (Movendo uma imagem com as teclas de seta) Escreva um programa que desenha o
bitmap ball.bmp no centro da tela. Quando o usuário pressiona uma das teclas de seta, o
bitmap deve se mover dez pixels nessa direção.
E.6 (Movendo uma imagem com as teclas de seta) Modifique o programa do Exercício
E.5 de modo que a bola só se mova uma vez para cada vez que uma tecla de seta é
pressionada. Se o usuário segurar uma tecla de seta, a bola deve se mover uma vez e depois
parar até que o usuário solte e pressione a tecla novamente.
E.7 (Movendo uma imagem com as teclas de seta) Modifique o programa do Exercício
E.5 de modo que, se o usuário segurar uma tecla de seta, a bola só se mova uma vez a cada
segundo.
E.8 (Terminando o jogo Pong em 21) Modifique o jogo de Pong da Figura E.21 de
modo que, quando o jogador atingir 21 pontos, o jogo termine e mostre uma mensagem que
o jogador da esquerda ou da direita venceu.
E.9 (Movendo a bola mais rápido em um rali longo) Na maioria dos jogos de Pong,
quando um rali entre os dois jogadores dura muito tempo, a bola começa a correr mais, para
impedir um impasse. Modifique o jogo de Pong da Figura E.21 de modo que a velocidade
da bola aumente para cada dez vezes que ela é atingida em um rali. Quando um dos
jogadores fizer um ponto, a bola deverá retornar à sua velocidade original.
E.10 (Alterando as velocidades da raquete) Alguns jogos de Pong também modificam a
velocidade de uma ou ambas as raquetes do jogador em um esforço para manter o jogo
equilibrado. Modifique o jogo de Pong da Figura E.21 de modo que, quando um jogador
tiver uma dianteira de pelo menos 5 pontos, sua raquete comece a diminuir a velocidade.
Quanto maior a dianteira do jogador, mais lenta sua raquete deverá ser. Se a dianteira do
jogador cair para menos de 5 pontos, sua raquete deverá retornar à velocidade normal.
E.11 (Incluindo um recurso de pausa) Os videogames normalmente possuem um recurso
de ―pausa‖, que permite que um jogador interrompa um jogo em andamento e continue mais
tarde. Modifique o jogo de Pong da Figura E.21 de modo que pressionar a tecla P gerencie
uma pausa no jogo e interrompa o movimento da bola e das raquetes. A pausa no jogo
também deve fazer com que a mensagem "PAUSADO" apareça no centro da tela. Se o jogo
estiver pausado, pressionar a tecla R deverá apagar a mensagem "PAUSADO" da tela e retomar
o jogo.
E.12 (Indicando quando a bola atinge uma raquete) Modifique o programa de Pong da
Figura E.21 de modo que, quando a bola quicar em uma parede ou na raquete, a palavra
"BOING!" apareça em azul no ponto onde a bola tocou e depois desapareça. O texto não deverá
simplesmente sumir ele deverá passar gradualmente para branco.
E.13 (Fornecendo opções de velocidade de bola e raquete) Modifique o jogo de Pong da
Figura E.21 de modo que, antes que o jogo comece, um menu apareça na tela para permitir
que os jogadores escolham dentre várias velocidades diferentes de bola e raquete. Isso é
mais difícil do que parece! O Allegro não tem métodos de entrada de texto. Você terá de
encontrar outro método para resolver esse problema.