Jogo Interativo de Adivinhação de Números
Este projeto apresenta um jogo de adivinhação de números implementado utilizando o microcontrolador STC89C52RC, um display LCD1602 e uma matriz de teclas 4x4. O objetivo é implementar um sistema completo de interação com o usuário, incluindo geração de números aleatórios, leitura de entrada via matriz de teclas e exibição de informações em tempo real.
Componentes e Especificações
O projeto utiliza os seguintes recursos de hardware:
- Microcontrolador: STC89C52RC
- Frequência de operação: 12T a 11.0592MHz
- Display: LCD1602 (16 colunas x 2 linhas)
- Interface de entrada: Matriz de teclas 4x4
Descrição do Jogo
O jogadordefine uma faixa de números (entre 1 e 9999) e o sistema gera um número aleatório dentro dessa faixa. O jogador tenta adivinhar o número, e o sistema fornece dicas indicando se o número inserido é maior ou menor que o valor secreto. O jogo continua até que o jogador acerte o número, mostrando a quantidade de tentativas realizadas.
Análise de Funcionamento
1. Entrada de Dados e Limpeza
Para capturar números digitados pelo usuário, o sistema utiliza operações de deslocamento decimal e adição progressiva. Cada novo dígito é integrado ao valor total através da fórmula: valor = valor × 10 + dígito, permitindo a construção de números de até quatro dígitos.
2. Geração de Números Aleatórios
O sistema gera números aleatórios dentro da faixa definida pelo usuário (1 a 9999). Para melhorar a aleatoriedade, são utilizados múltiplos componentes do temporizador como semente, incluindo valores dos registradores TH0 e TL0 em combinação com operações matemáticas. Embora ainda seja um gerador pseudoaleatório, esta abordagem produz resultados suficientemente imprevisíveis para o propósito do jogo.
3. Limitações de Faixa
Devido às dimensões do display LCD1602, a faixa de números foi limitada a 1-9999 (máximo de 4 dígitos). Ao definir os limites, o sistema verifica se a diferença entre máximo e mínimo é de pelo menos 2 unidades, garantindo uma faixa válida para o jogo.
4. Detecção de Teclas
A matriz de teclas é explorada através de varredura行列 (linha-coluna). O temporizador 0 é configurado para verificar o estado das teclas a cada 20ms, evitando bloqueios na execução do programa principal.
Implementação dos Módulos
1. Biblioteca LCD1602
O módulo de controle do display LCD1602 oferece funções para escrita de comandos, dados, posicionamento do cursor e exibição de diferentse formatos numéricos.
Arquivo de cabeçalho (lcd1602.h):
#ifndef LCD1602_H
#define LCD1602_H
void LCD_Inicializar(void);
void LCD_EscreverComando(unsigned char cmd);
void LCD_EscreverDado(unsigned char dado);
void LCD_PosicionarCursor(unsigned char linha, unsigned char coluna);
void LCD_ExibirCaractere(unsigned char linha, unsigned char coluna, char caractere);
void LCD_ExibirTexto(unsigned char linha, unsigned char coluna, char *texto);
void LCD_ExibirNumero(unsigned char linha, unsigned char coluna, unsigned int valor, unsigned char digitos);
void LCD_ExibirNumeroSigned(unsigned char linha, unsigned char coluna, int valor, unsigned char digitos);
void LCD_ExibirHex(unsigned char linha, unsigned char coluna, unsigned int valor, unsigned char digitos);
void LCD_ExibirBinario(unsigned char linha, unsigned char coluna, unsigned int valor, unsigned char digitos);
void LCD_Limpar(void);
void LCD_ControlarCursor(unsigned char estado);
void LCD_ExibirTexto16(unsigned char linha, unsigned char coluna, char *texto);
#endif
Arquivo de implementação (lcd1602.c):
#include <REGX52.H>
/* Configuração de pinos */
sbit LCD_RS = P2^6;
sbit LCD_RW = P2^5;
sbit LCD_EN = P2^7;
#define LCD_PORTA_DADOS P0
/* Função de atraso - aproximadamente 50μs */
void Atraso50us(void)
{
unsigned char contador;
contador = 20;
while(--contador);
}
/* Função de atraso - aproximadamente 2ms */
void Atraso2ms(void)
{
unsigned char i, j;
i = 4;
j = 146;
do {
while(--j);
} while(--i);
}
/* Escreve comando no LCD */
void LCD_EscreverComando(unsigned char cmd)
{
LCD_RS = 0;
LCD_RW = 0;
LCD_PORTA_DADOS = cmd;
LCD_EN = 1;
Atraso50us();
LCD_EN = 0;
Atraso50us();
}
/* Escreve dado no LCD */
void LCD_EscreverDado(unsigned char dado)
{
LCD_RS = 1;
LCD_RW = 0;
LCD_PORTA_DADOS = dado;
LCD_EN = 1;
Atraso50us();
LCD_EN = 0;
Atraso50us();
}
/* Posiciona o cursor */
void LCD_PosicionarCursor(unsigned char linha, unsigned char coluna)
{
if(linha == 1) {
LCD_EscreverComando(0x80 | (coluna - 1));
} else if(linha == 2) {
LCD_EscreverComando(0x80 | (coluna - 1 + 0x40));
}
}
/* Inicializa o LCD */
void LCD_Inicializar(void)
{
LCD_EscreverComando(0x38);
LCD_EscreverComando(0x0C);
LCD_EscreverComando(0x06);
LCD_EscreverComando(0x01);
Atraso2ms();
}
/* Exibe caractere na posição especificada */
void LCD_ExibirCaractere(unsigned char linha, unsigned char coluna, char caractere)
{
LCD_PosicionarCursor(linha, coluna);
LCD_EscreverDado(caractere);
}
/* Exibe string a partir da posição especificada */
void LCD_ExibirTexto(unsigned char linha, unsigned char coluna, char *texto)
{
unsigned char indice;
LCD_PosicionarCursor(linha, coluna);
for(indice = 0; texto[indice] != '\0'; indice++) {
LCD_EscreverDado(texto[indice]);
}
}
/* Calcula potência de X elevado a Y */
unsigned int Potencia(unsigned int X, unsigned char Y)
{
unsigned char i;
unsigned int resultado = 1;
for(i = 0; i < Y; i++) {
resultado *= X;
}
return resultado;
}
/* Exibe número sem sinal */
void LCD_ExibirNumero(unsigned char linha, unsigned char coluna, unsigned int valor, unsigned char digitos)
{
unsigned char i;
LCD_PosicionarCursor(linha, coluna);
for(i = digitos; i > 0; i--) {
LCD_EscreverDado(valor / Potencia(10, i-1) % 10 + '0');
}
}
/* Exibe número com sinal */
void LCD_ExibirNumeroSigned(unsigned char linha, unsigned char coluna, int valor, unsigned char digitos)
{
unsigned char i;
unsigned int valorAbs;
LCD_PosicionarCursor(linha, coluna);
if(valor >= 0) {
LCD_EscreverDado('+');
valorAbs = valor;
} else {
LCD_EscreverDado('-');
valorAbs = -valor;
}
for(i = digitos; i > 0; i--) {
LCD_EscreverDado(valorAbs / Potencia(10, i-1) % 10 + '0');
}
}
/* Exibe número em hexadecimal */
void LCD_ExibirHex(unsigned char linha, unsigned char coluna, unsigned int valor, unsigned char digitos)
{
unsigned char i, digito;
LCD_PosicionarCursor(linha, coluna);
for(i = digitos; i > 0; i--) {
digito = valor / Potencia(16, i-1) % 16;
if(digito < 10) {
LCD_EscreverDado(digito + '0');
} else {
LCD_EscreverDado(digito - 10 + 'A');
}
}
}
/* Exibe número em binário */
void LCD_ExibirBinario(unsigned char linha, unsigned char coluna, unsigned int valor, unsigned char digitos)
{
unsigned char i;
LCD_PosicionarCursor(linha, coluna);
for(i = digitos; i > 0; i--) {
LCD_EscreverDado(valor / Potencia(2, i-1) % 2 + '0');
}
}
/* Limpa o display */
void LCD_Limpar(void)
{
LCD_EscreverComando(0x01);
Atraso2ms();
}
/* Controla visibilidade do cursor */
void LCD_ControlarCursor(unsigned char estado)
{
if(estado) {
LCD_EscreverComando(0x0E);
} else {
LCD_EscreverComando(0x0C);
}
}
2. Módulo de Matriz de Teclas
A biblioteca de varredura da matriz de teclas oferece detecção de pressão, liberação e pressão contínua.
Arquivo de cabeçalho (matriz_telas.h):
#ifndef MATRIZ_TECLAS_H
#define MATRIZ_TECLAS_H
unsigned char Matriz_Teclas_Ler(void);
void Matriz_Teclas_Atualizar(void);
#endif
Arquivo de implementação (matriz_teclas.c):
#include <REGX52.H>
#define PORTA_MATRIZ P1
unsigned char CodigoTecla;
/* Obtém código da tecla pressionada */
unsigned char Matriz_Teclas_Ler(void)
{
unsigned char temp = CodigoTecla;
CodigoTecla = 0;
return temp;
}
/* Captura estado atual das teclas */
unsigned char Tecla_EstadoAtual(void)
{
unsigned char valor = 0;
unsigned char i = 0;
PORTA_MATRIZ = 0x0F;
i = 2;
while(i--);
if(PORTA_MATRIZ != 0x0F) {
PORTA_MATRIZ = 0x0F;
i = 2;
while(i--);
switch(PORTA_MATRIZ) {
case 0x07: valor = 1; break;
case 0x0B: valor = 2; break;
case 0x0D: valor = 3; break;
case 0x0E: valor = 4; break;
default: break;
}
PORTA_MATRIZ = 0xF0;
i = 2;
while(i--);
switch(PORTA_MATRIZ) {
case 0x70: valor = valor; break;
case 0xB0: valor = valor + 4; break;
case 0xD0: valor = valor + 8; break;
case 0xE0: valor = valor + 12; break;
default: break;
}
} else {
valor = 0;
}
return valor;
}
/* Função de tratamento chamada por interrupção */
void Matriz_Teclas_Atualizar(void)
{
static unsigned char EstadoAtual, EstadoAnterior;
static unsigned int ContadorPressao;
EstadoAnterior = EstadoAtual;
EstadoAtual = Tecla_EstadoAtual();
/* Detecção de pressão */
if(EstadoAnterior == 0) {
switch(EstadoAtual) {
case 1: CodigoTecla = 1; break;
case 2: CodigoTecla = 2; break;
case 3: CodigoTecla = 3; break;
case 4: CodigoTecla = 4; break;
case 5: CodigoTecla = 5; break;
case 6: CodigoTecla = 6; break;
case 7: CodigoTecla = 7; break;
case 8: CodigoTecla = 8; break;
case 9: CodigoTecla = 9; break;
case 10: CodigoTecla = 10; break;
case 11: CodigoTecla = 11; break;
case 12: CodigoTecla = 12; break;
case 13: CodigoTecla = 13; break;
case 14: CodigoTecla = 14; break;
case 15: CodigoTecla = 15; break;
case 16: CodigoTecla = 16; break;
default: break;
}
}
/* Detecção de pressão contínua (longa) */
if(EstadoAnterior && EstadoAtual) {
ContadorPressao++;
if(ContadorPressao % 50 == 0) {
if(EstadoAnterior == 1 && EstadoAtual == 1) CodigoTecla = 17;
else if(EstadoAnterior == 2 && EstadoAtual == 2) CodigoTecla = 18;
else if(EstadoAnterior == 3 && EstadoAtual == 3) CodigoTecla = 19;
else if(EstadoAnterior == 4 && EstadoAtual == 4) CodigoTecla = 20;
else if(EstadoAnterior == 5 && EstadoAtual == 5) CodigoTecla = 21;
else if(EstadoAnterior == 6 && EstadoAtual == 6) CodigoTecla = 22;
else if(EstadoAnterior == 7 && EstadoAtual == 7) CodigoTecla = 23;
else if(EstadoAnterior == 8 && EstadoAtual == 8) CodigoTecla = 24;
else if(EstadoAnterior == 9 && EstadoAtual == 9) CodigoTecla = 25;
else if(EstadoAnterior == 10 && EstadoAtual == 10) CodigoTecla = 26;
else if(EstadoAnterior == 11 && EstadoAtual == 11) CodigoTecla = 27;
else if(EstadoAnterior == 12 && EstadoAtual == 12) CodigoTecla = 28;
else if(EstadoAnterior == 13 && EstadoAtual == 13) CodigoTecla = 29;
else if(EstadoAnterior == 14 && EstadoAtual == 14) CodigoTecla = 30;
else if(EstadoAnterior == 15 && EstadoAtual == 15) CodigoTecla = 31;
else if(EstadoAnterior == 16 && EstadoAtual == 16) CodigoTecla = 32;
}
} else {
ContadorPressao = 0;
}
/* Detecção de liberação */
if(EstadoAtual == 0) {
switch(EstadoAnterior) {
case 1: CodigoTecla = 33; break;
case 2: CodigoTecla = 34; break;
case 3: CodigoTecla = 35; break;
case 4: CodigoTecla = 36; break;
case 5: CodigoTecla = 37; break;
case 6: CodigoTecla = 38; break;
case 7: CodigoTecla = 39; break;
case 8: CodigoTecla = 40; break;
case 9: CodigoTecla = 41; break;
case 10: CodigoTecla = 42; break;
case 11: CodigoTecla = 43; break;
case 12: CodigoTecla = 44; break;
case 13: CodigoTecla = 45; break;
case 14: CodigoTecla = 46; break;
case 15: CodigoTecla = 47; break;
case 16: CodigoTecla = 48; break;
default: break;
}
}
}
3. Módulo de Temporizador
Configuração do temporizador 0 para geração de interrupções periódicas.
Arquivo de cabeçalho (temporizador0.h):
#ifndef TEMPORIZADOR0_H
#define TEMPORIZADOR0_H
void Temporizador0_Iniciar(void);
#endif
Arquivo de implementação (temporizador0.c):
#include <REGX52.H>
/* Inicializa temporizador 0 para interrupção a cada 10ms */
void Temporizador0_Iniciar(void)
{
TMOD &= 0xF0;
TMOD |= 0x01;
TL0 = 0x00;
TH0 = 0xDC;
TF0 = 0;
TR0 = 1;
ET0 = 1;
EA = 1;
PT0 = 0;
}
4. Programa Principal
O código principal implementa a lógica completa do jogo, incluindo controle de estados, entrada do usuário e feedback visual.
#include <REGX52.H>
#include "lcd1602.h"
#include "matriz_telas.h"
#include "temporizador0.h"
#include <STDLIB.H>
unsigned char TeclaRecebida;
unsigned char EstadoJogo;
unsigned char ExecucaoUnica = 1;
unsigned char DigitosEntrada;
unsigned char UltimosDigitos;
bit FlagConfirmacao;
unsigned char DigitoAtual;
bit FlagConfigMin;
bit FlagConfigMax;
unsigned int ValorMinimo;
unsigned int ValorMaximo;
unsigned char DigitosMin;
unsigned char DigitosMax;
bit FlagVerificacao;
bit FlagReentrada;
unsigned int NumeroJogador;
unsigned char DigitosJogador;
unsigned int NumeroSorteado;
unsigned char DigitosSorteado;
unsigned int Tentativas;
unsigned char DigitosTentativas;
/* Função de atraso em milissegundos */
void Atraso(unsigned int ms)
{
unsigned char i, j;
while(ms--) {
i = 2;
j = 199;
do {
while(--j);
} while(--i);
}
}
/* Função principal */
void main()
{
Temporizador0_Iniciar();
LCD_Inicializar();
while(1) {
TeclaRecebida = Matriz_Teclas_Ler();
if(TeclaRecebida) {
if(EstadoJogo == 0) {
if(TeclaRecebida >= 33 && TeclaRecebida <= 48) {
EstadoJogo = 1;
ExecucaoUnica = 1;
}
} else if(EstadoJogo == 1) {
if(TeclaRecebida >= 33 && TeclaRecebida <= 42) {
if(DigitosEntrada < 4) {
if(DigitosEntrada != 0 || TeclaRecebida != 42) {
DigitoAtual = (TeclaRecebida - 32) % 10;
DigitosEntrada++;
}
}
}
if(TeclaRecebida == 27) {
ExecucaoUnica = 1;
}
if(TeclaRecebida == 43) {
FlagReentrada = 1;
}
if(TeclaRecebida == 44) {
if(DigitosEntrada) FlagConfirmacao = 1;
}
} else if(EstadoJogo == 2) {
if(TeclaRecebida >= 33 && TeclaRecebida <= 42) {
if(DigitosEntrada < 4) {
if(DigitosEntrada != 0 || TeclaRecebida != 42) {
DigitoAtual = (TeclaRecebida - 32) % 10;
DigitosEntrada++;
}
}
}
if(TeclaRecebida == 27) {
EstadoJogo = 1;
ExecucaoUnica = 1;
}
if(TeclaRecebida == 43) {
FlagReentrada = 1;
}
if(TeclaRecebida == 44) {
if(DigitosEntrada) FlagConfirmacao = 1;
}
} else if(EstadoJogo == 3) {
if(TeclaRecebida == 43) {
EstadoJogo = 1;
ExecucaoUnica = 1;
}
}
}
/* Estado 0: Tela inicial */
if(EstadoJogo == 0) {
if(ExecucaoUnica) {
ExecucaoUnica = 0;
LCD_Limpar();
LCD_ExibirTexto(1, 3, "Guess Number");
}
}
/* Estado 1: Configuração de faixa */
else if(EstadoJogo == 1) {
if(ExecucaoUnica) {
ExecucaoUnica = 0;
UltimosDigitos = 0;
DigitosEntrada = 0;
FlagConfirmacao = 0;
FlagVerificacao = 0;
FlagReentrada = 0;
FlagConfigMin = 1;
FlagConfigMax = 0;
ValorMinimo = 0;
ValorMaximo = 0;
DigitosMin = 0;
DigitosMax = 0;
Tentativas = 0;
LCD_Limpar();
LCD_ExibirTexto(1, 1, "Set Range");
LCD_ExibirTexto(2, 1, "Min:");
LCD_ExibirTexto(2, 9, "Max:");
LCD_PosicionarCursor(2, 5);
LCD_ControlarCursor(1);
}
if(UltimosDigitos != DigitosEntrada) {
UltimosDigitos = DigitosEntrada;
if(FlagConfigMax) {
LCD_ExibirNumero(2, 12 + DigitosEntrada, DigitoAtual, 1);
ValorMaximo *= 10;
ValorMaximo += DigitoAtual;
DigitosMax++;
}
if(FlagConfigMin) {
LCD_ExibirNumero(2, 4 + DigitosEntrada, DigitoAtual, 1);
ValorMinimo *= 10;
ValorMinimo += DigitoAtual;
DigitosMin++;
}
}
if(FlagConfirmacao || DigitosEntrada == 4) {
if(FlagConfigMax) {
FlagConfigMax = 0;
FlagConfirmacao = 0;
UltimosDigitos = 0;
DigitosEntrada = 0;
FlagVerificacao = 1;
}
if(FlagConfigMin) {
FlagConfigMin = 0;
FlagConfirmacao = 0;
UltimosDigitos = 0;
DigitosEntrada = 0;
LCD_PosicionarCursor(2, 13);
FlagConfigMax = 1;
}
}
if(FlagVerificacao) {
FlagVerificacao = 0;
if(ValorMinimo + 1 < ValorMaximo) {
EstadoJogo = 2;
ExecucaoUnica = 1;
} else {
ExecucaoUnica = 1;
LCD_ExibirTexto(1, 14, "ERR");
Atraso(1000);
}
}
if(FlagReentrada) {
FlagReentrada = 0;
if(FlagConfigMin) {
UltimosDigitos = 0;
DigitosEntrada = 0;
ValorMinimo = 0;
DigitosMin = 0;
LCD_ExibirTexto(2, 5, " ");
LCD_PosicionarCursor(2, 5);
}
if(FlagConfigMax) {
UltimosDigitos = 0;
DigitosEntrada = 0;
ValorMaximo = 0;
DigitosMax = 0;
LCD_ExibirTexto(2, 13, " ");
LCD_PosicionarCursor(2, 13);
}
}
}
/* Estado 2: Jogo em andamento */
else if(EstadoJogo == 2) {
if(ExecucaoUnica) {
ExecucaoUnica = 0;
UltimosDigitos = 0;
DigitosEntrada = 0;
FlagConfirmacao = 0;
srand(TL0);
NumeroSorteado = ((rand() + TH0 * TL0 + TL0 / TH0) * (TH0 - TL0) % (ValorMaximo - ValorMinimo - 1) + ValorMinimo + 1);
LCD_Limpar();
LCD_ExibirTexto(1, 1, "Range:");
LCD_ExibirNumero(1, 8, ValorMinimo, DigitosMin);
LCD_ExibirCaractere(1, 8 + DigitosMin, '~');
LCD_ExibirNumero(1, 9 + DigitosMin, ValorMaximo, DigitosMax);
LCD_PosicionarCursor(2, 1);
}
if(UltimosDigitos != DigitosEntrada) {
UltimosDigitos = DigitosEntrada;
LCD_ExibirNumero(2, DigitosEntrada, DigitoAtual, 1);
NumeroJogador *= 10;
NumeroJogador += DigitoAtual;
DigitosJogador++;
}
if(FlagConfirmacao || DigitosEntrada == 4) {
FlagConfirmacao = 0;
UltimosDigitos = 0;
DigitosEntrada = 0;
Tentativas++;
if(NumeroJogador == NumeroSorteado) {
EstadoJogo = 3;
ExecucaoUnica = 1;
} else if(NumeroJogador < NumeroSorteado && NumeroJogador > ValorMinimo) {
ValorMinimo = NumeroJogador;
DigitosMin = DigitosJogador;
} else if(NumeroJogador > NumeroSorteado && NumeroJogador < ValorMaximo) {
ValorMaximo = NumeroJogador;
DigitosMax = DigitosJogador;
} else {
Tentativas--;
LCD_ExibirTexto(2, 14, "ERR");
Atraso(1000);
LCD_ExibirTexto(2, 14, " ");
}
NumeroJogador = 0;
DigitosJogador = 0;
LCD_ExibirTexto(1, 8, " ");
LCD_ExibirNumero(1, 8, ValorMinimo, DigitosMin);
LCD_ExibirCaractere(1, 8 + DigitosMin, '~');
LCD_ExibirNumero(1, 9 + DigitosMin, ValorMaximo, DigitosMax);
LCD_PosicionarCursor(2, 1);
UltimosDigitos = 0;
DigitosEntrada = 0;
LCD_ExibirTexto(2, 1, " ");
LCD_PosicionarCursor(2, 1);
}
if(FlagReentrada) {
FlagReentrada = 0;
UltimosDigitos = 0;
DigitosEntrada = 0;
NumeroJogador = 0;
DigitosJogador = 0;
LCD_ExibirTexto(2, 1, " ");
LCD_PosicionarCursor(2, 1);
}
}
/* Estado 3: Vitória */
else if(EstadoJogo == 3) {
if(ExecucaoUnica) {
ExecucaoUnica = 0;
LCD_Limpar();
LCD_ExibirTexto(1, 1, "Yes!");
LCD_ExibirTexto(1, 6, "Number:");
if(NumeroSorteado / 1000) DigitosSorteado = 4;
else if(NumeroSorteado / 100) DigitosSorteado = 3;
else if(NumeroSorteado / 10) DigitosSorteado = 2;
else DigitosSorteado = 1;
LCD_ExibirNumero(1, 13, NumeroSorteado, DigitosSorteado);
LCD_ExibirTexto(2, 1, "Times:");
if(Tentativas / 1000) DigitosTentativas = 4;
else if(Tentativas / 100) DigitosTentativas = 3;
else if(Tentativas / 10) DigitosTentativas = 2;
else DigitosTentativas = 1;
LCD_ExibirNumero(2, 7, Tentativas, DigitosTentativas);
LCD_ControlarCursor(0);
}
}
}
}
/* Rotina de interrupção do temporizador 0 */
void Temporizador0_Tratamento() interrupt 1
{
static unsigned char ContadorT0;
TL0 = 0x00;
TH0 = 0xDC;
ContadorT0++;
if(ContadorT0 >= 2) {
ContadorT0 = 0;
Matriz_Teclas_Atualizar();
}
}
Controles do Jogo
| S1-S9 | Dígitos 1-9 |
|---|---|
| S10 | Dígito 0 |
| S11 | Limpar/Retornar |
| S12 | Confirmar |
Observações Finais
A implementação atual utiliza uma abordagem pseudoaleatória baseada em registradores do temporizador. Para aplicações que exigem maior aleatoriedade, considere a adição de fontes de entropia externas ou algoritmos de randomização mais sofisticados. O sistema de detecção de teclas implementa múltiplos estados (pressão, liberação, pressão contínua), proporcionando uma experiência de usuário rica e responsiva.