Introdução
Flappy Bird é um jogo mobile popular e viciante que ganhou imensa popularidade por sua jogabilidade simples, mas desafiadora. Neste projeto, aprenderemos como implementar nossa própria versão do Flappy Bird usando a linguagem de programação C.
Ao seguir este projeto, você irá:
- Aprender como usar a biblioteca
ncursespara desenho de tela baseado em texto. - Adquirir conhecimento sobre estruturas de dados e chamadas de sistema no Linux.
- Ganhar experiência no tratamento de eventos de teclado e atualizações em tempo real em um programa C.
👀 Pré-visualização

🎯 Tarefas
Neste projeto, você aprenderá:
- Como implementar a versão baseada em caracteres do Flappy Bird usando C.
- Como lidar com eventos de teclado para controlar o movimento do pássaro.
- Como criar a ilusão de movimento para frente movendo os obstáculos da direita para a esquerda.
- Como usar a biblioteca
ncursespara desenhar a interface de caracteres.
🏆 Conquistas
Após concluir este projeto, você será capaz de:
- Demonstrar proficiência na linguagem de programação C.
- Desenvolver habilidades no tratamento de eventos de teclado.
- Implementar atualizações em tempo real em um programa C.
- Utilizar a biblioteca
ncursespara desenho de tela baseado em texto. - Compreender estruturas de dados e chamadas de sistema no Linux.
Conhecimentos Básicos
Nosso projeto requer algum conhecimento de estruturas de dados e envolve algumas chamadas de sistema no Linux.
Além disso, também usamos uma biblioteca de desenho de tela baseada em texto chamada ncurses. Portanto, ao compilar, a opção -lcurses precisa ser adicionada.
Ideias de Design
Para implementar a versão baseada em caracteres do Flappy Bird, começamos com a implementação dos três pontos-chave a seguir:
- O programa deve ser capaz de responder a eventos de teclado.
- A interface de caracteres deve ser capaz de atualizar em tempo real.
- O pássaro deve dar a ilusão visual de voar para frente.
Para os três problemas acima, nossas soluções são as seguintes:
- Use as interfaces do sistema fornecidas pelo Linux para capturar eventos de teclado.
- Use as funções da biblioteca ncurses para desenhar a interface de caracteres.
- Para criar a ilusão do pássaro voando para frente:
A abordagem mais direta é fazer o pássaro se mover da esquerda para a direita na direção horizontal, mas isso faria com que o pássaro excedesse o limite direito em algum momento.
Em vez disso, vamos pensar ao contrário: quando uma pessoa vê a paisagem fora do carro enquanto viaja para frente, ela parece estar se movendo para trás (o movimento é relativo). Então, deixamos os obstáculos se moverem da direita para a esquerda, o que atinge o mesmo efeito visual e evita o problema do pássaro exceder o limite.
Definir Constantes
Primeiramente, abra o terminal Xfce e execute o seguinte comando para instalar a biblioteca ncurses:
sudo apt update
sudo apt-get install libncurses5-dev
Navegue até o diretório ~/project e crie o arquivo do projeto flappy_bird.c:
cd ~/project
touch flappy_bird.c
Em seguida, precisamos escrever o código C. O primeiro passo é incluir os arquivos de cabeçalho:
## include <curses.h>
## include <stdlib.h>
## include <signal.h>
## include <sys/time.h>
Antes de escrever a função main(), vamos completar algumas tarefas básicas. Como estamos trabalhando com uma interface de caracteres do terminal, os caracteres ASCII são essenciais. Portanto, precisamos definir algumas constantes.
Usaremos * para representar os pilares no fundo e O para representar o pássaro. O código é o seguinte:
## define CHAR_BIRD 'O' // Define o caractere do pássaro
## define CHAR_STONE '*' // Define as pedras que compõem os pilares
## define CHAR_BLANK ' ' // Define o caractere vazio
Os pilares no fundo serão armazenados usando uma lista encadeada simples. A struct é definida da seguinte forma:
typedef struct node {
int x, y;
struct node *next;
}node, *Node;
Vamos também definir algumas variáveis globais:
Node head, tail;
int bird_x, bird_y;
int ticker;
Agora, declararemos as funções que criaremos:
void init(); // Função de inicialização que gerencia as tarefas de inicialização do jogo
void init_bird(); // Inicializa as coordenadas de posição do pássaro
void init_draw(); // Inicializa o fundo
void init_head(); // Inicializa a cabeça da lista encadeada que armazena os pilares
void init_wall(); // Inicializa a lista encadeada que armazena os pilares
void drop(int sig); // Função de recebimento de sinal para receber sinais do sistema e mover os pilares da direita para a esquerda
int set_ticker(int n_msec); // Define o intervalo de tick do temporizador do kernel
Problema de Tempo (Timing)
Agora, vamos resolver o problema de como fazer o fundo se mover em um intervalo regular. Usaremos a funcionalidade fornecida pelo sistema Linux, nomeadamente sinais.
Não tem certeza do que é um sinal? Sem problemas, você pode pensar nisso como um temporizador no kernel do Linux que envia um sinal para o nosso programa a cada certo período de tempo. Nossa função de tratamento de sinal drop(int sig) será executada automaticamente quando o sinal for recebido. Só precisamos mover o pilar na função drop(int sig). Além disso, como o sinal é enviado pelo kernel do Linux, não haverá nenhum bloqueio da nossa recepção de sinal do teclado devido ao recebimento do sinal.
Agora, vamos implementar nosso código e definir o período do temporizador do kernel usando a função set_ticker(int n_msec):
int set_ticker(int n_msec)
{
struct itimerval timeset;
long n_sec, n_usec;
n_sec = n_msec / 1000;
n_usec = (n_msec % 1000) * 1000L;
timeset.it_interval.tv_sec = n_sec;
timeset.it_interval.tv_usec = n_usec;
timeset.it_value.tv_sec = n_sec;
timeset.it_value.tv_usec = n_usec;
return setitimer(ITIMER_REAL, ×et, NULL);
}
Função de tratamento de sinal drop(int sig):
void drop(int sig)
{
int j;
Node tmp, p;
// Clear the symbol at the original bird position
move(bird_y, bird_x);
addch(CHAR_BLANK);
refresh();
// Update the position of the bird and refresh the screen
bird_y++;
move(bird_y, bird_x);
addch(CHAR_BIRD);
refresh();
// End the game if the bird collides with the pillar
if((char)inch() == CHAR_STONE)
{
set_ticker(0);
sleep(1);
endwin();
exit(0);
}
// Check if the first wall goes beyond the boundary
p = head->next;
if(p->x < 0)
{
head->next = p->next;
free(p);
tmp = (node *)malloc(sizeof(node));
tmp->x = 99;
tmp->y = rand() % 11 + 5;
tail->next = tmp;
tmp->next = NULL;
tail = tmp;
ticker -= 10; // Accelerate
set_ticker(ticker);
}
// Draw a new pillar
for(p = head->next; p->next != NULL; p->x--, p = p->next)
{
// Replace CHAR_STONE with CHAR_BLANK
for(j = 0; j < p->y; j++)
{
move(j, p->x);
addch(CHAR_BLANK);
refresh();
}
for(j = p->y+5; j <= 23; j++)
{
move(j, p->x);
addch(CHAR_BLANK);
refresh();
}
if(p->x-10 >= 0 && p->x < 80)
{
for(j = 0; j < p->y; j++)
{
move(j, p->x-10);
addch(CHAR_STONE);
refresh();
}
for(j = p->y + 5; j <= 23; j++)
{
move(j, p->x-10);
addch(CHAR_STONE);
refresh();
}
}
}
tail->x--;
}
Na função de tratamento de sinal, movemos o fundo para frente por uma coluna e, ao mesmo tempo, deixamos o pássaro cair por uma linha. Também verificamos se o pássaro colide com um pilar. Se isso acontecer, o jogo termina.
Função main()
Na função main(), primeiro chamamos a função de inicialização init(), e então entramos no loop while(). O loop consiste principalmente em três partes:
- Verificar a entrada do usuário: Se a tecla "w" ou a barra de espaço for pressionada, o pássaro se moverá para cima duas linhas. Se a tecla "q" for pressionada, o jogo será encerrado. Se a tecla "z" for pressionada, o jogo será pausado.
- Mover o pássaro e redesenhá-lo.
- Verificar se o pássaro atinge os canos.
Vamos dar uma olhada no código:
int main()
{
char ch;
init();
while(1)
{
ch = getch(); // Get keyboard input
if(ch == ' ' || ch == 'w' || ch == 'W') // If spacebar or "w" key is pressed
{
// Move the bird and redraw it
move(bird_y, bird_x);
addch(CHAR_BLANK);
refresh();
bird_y--;
move(bird_y, bird_x);
addch(CHAR_BIRD);
refresh();
// If the bird hits the pipes, end the game
if((char)inch() == CHAR_STONE)
{
set_ticker(0);
sleep(1);
endwin();
exit(0);
}
}
else if(ch == 'z' || ch == 'Z') // Pause
{
set_ticker(0);
do
{
ch = getch();
} while(ch != 'z' && ch != 'Z');
set_ticker(ticker);
}
else if(ch == 'q' || ch == 'Q') // Quit
{
sleep(1);
endwin();
exit(0);
}
}
return 0;
}
Na função main(), primeiro inicializamos a tela e, em seguida, recebemos a entrada do teclado em um loop. Se a tecla "w" ou a barra de espaço for pressionada, o pássaro se moverá para cima duas linhas. Se a tecla "q" for pressionada, o jogo será encerrado. Se a tecla "z" for pressionada, o jogo será pausado.
Agora, vamos dar uma olhada na função init():
void init()
{
initscr();
cbreak();
noecho();
curs_set(0);
srand(time(0));
signal(SIGALRM, drop);
init_bird();
init_head();
init_wall();
init_draw();
sleep(1);
ticker = 500;
set_ticker(ticker);
}
A função init() primeiro inicializa a tela usando funções fornecidas por ncurses. Em seguida, ela chama várias sub-funções para realizar inicializações específicas. Observe que instalamos uma função de tratamento de sinal drop() e definimos o intervalo do temporizador.
Vamos analisar cada sub-função de inicialização.
A função init_bird() inicializa a posição do pássaro:
void init_bird()
{
bird_x = 5;
bird_y = 15;
move(bird_y, bird_x);
addch(CHAR_BIRD);
refresh();
sleep(1);
}
As funções init_head() e init_wall() inicializam uma lista encadeada para armazenar os canos:
void init_head()
{
Node tmp;
tmp = (node *)malloc(sizeof(node));
tmp->next = NULL;
head = tmp;
tail = head;
}
void init_wall()
{
int i;
Node tmp, p;
p = head;
for(i = 0; i < 5; i++)
{
tmp = (node *)malloc(sizeof(node));
tmp->x = (i + 1) * 19;
tmp->y = rand() % 11 + 5;
p->next = tmp;
tmp->next = NULL;
p = tmp;
}
tail = p;
}
A função init_draw() inicializa a tela:
void init_draw()
{
Node p;
int i, j;
// Traverse the linked list
for(p = head->next; p->next != NULL; p = p->next)
{
// Draw the pipes
for(i = p->x; i > p->x-10; i--)
{
for(j = 0; j < p->y; j++)
{
move(j, i);
addch(CHAR_STONE);
}
for(j = p->y+5; j <= 23; j++)
{
move(j, i);
addch(CHAR_STONE);
}
}
refresh();
sleep(1);
}
}
Com isso, nosso jogo flappy_bird está completo.
Compilação e Execução
Execute o comando gcc para compilar:
cd ~/project
gcc -o flappy_bird flappy_bird.c -lcurses
./flappy_bird

Resumo
Neste projeto, usamos a linguagem de programação C para implementar um jogo Flappy Bird baseado em texto. Os alunos podem aprimorar ainda mais o jogo com base neste curso, como adicionar cores aos canos ou fazer com que as larguras dos canos mudem aleatoriamente.



