Requisitos — Blocky Bird (Flappy Bird com temática Minecraft)¶
Introdução¶
Blocky Bird é um clone de Flappy Bird em Python/Pygame com temática Minecraft. O jogador controla uma abelha voxel que voa entre colunas de blocos, com progressão de biomas (Overworld → Cave → Nether), efeitos sonoros e partículas de blocos. Todos os gráficos e sons são gerados por código — sem assets externos ou material protegido da Mojang.
Escopo da v2: além de tudo que a v1 entregou (jogo completo para desktop, ver specs/v1/), a v2 torna o jogo jogável em Android — celular/tablet, sempre em orientação retrato — no maior número possível de aparelhos, distribuído como APK instalável diretamente. Inclui também itens de qualidade e apresentação que não mudam o gameplay, adicionados como aumento de escopo após a entrega Android: (1) todo o código Python do projeto passa a ser verificado pela ferramenta ruff (lint + formatação), com conformidade obrigatória e checada em CI; (2) calibração do tamanho da fonte bitmap própria (R7.6), que estava grande demais em várias telas a ponto de textos se sobreporem entre si ou com outros elementos de UI; (3) um ícone do aplicativo com a personagem do jogo (a abelha voxel, R7.2), gerado por código, para a janela/executável do desktop e para o launcher do Android — incluindo o formato de ícone adaptativo exigido desde o Android 8.0 (API 26), para que a abelha não fique cortada nem distorcida pelas diferentes máscaras de ícone dos fabricantes (círculo, "squircle", quadrado arredondado). O desktop continua suportado; nenhum requisito da v1 é removido.
Notação: critérios de aceitação em formato EARS (QUANDO <evento>, O sistema DEVE <resposta>).
R1 — Controle do pássaro¶
User story: Como jogador, quero controlar o voo da abelha com um único comando, para que o jogo seja simples de aprender.
Critérios de aceitação¶
- QUANDO o jogador pressiona ESPAÇO, seta ↑, clica com o mouse ou pressiona o botão A do controle de Xbox, O sistema DEVE aplicar impulso vertical para cima ao pássaro.
- ENQUANTO o jogo está em estado JOGANDO, O sistema DEVE aplicar gravidade constante ao pássaro a cada frame.
- QUANDO o pássaro sobe, O sistema DEVE rotacionar o sprite para cima (máx. +30°); QUANDO cai, rotacionar gradualmente para baixo (máx. −60°).
- QUANDO o pássaro atinge o topo da tela, O sistema DEVE limitar sua posição ao topo sem encerrar o jogo.
R2 — Obstáculos¶
User story: Como jogador, quero desviar de colunas de blocos, para que exista desafio.
Critérios de aceitação¶
- ENQUANTO o jogo está em estado JOGANDO, O sistema DEVE gerar pares de colunas (superior/inferior) em intervalos fixos de distância horizontal.
- QUANDO um par de colunas é gerado, O sistema DEVE posicionar a abertura vertical em altura aleatória, com tamanho de abertura definido pelo bioma atual.
- ENQUANTO o jogo está em estado JOGANDO, O sistema DEVE mover as colunas da direita para a esquerda na velocidade definida pelo bioma atual.
- QUANDO uma coluna sai completamente da tela pela esquerda, O sistema DEVE removê-la da memória.
- QUANDO uma coluna é renderizada, O sistema DEVE desenhá-la como pilha de blocos texturizados do bioma atual (Overworld: terra/grama; Cave: pedra/pedregulho; Nether: netherrack/obsidiana).
R3 — Colisão e fim de jogo¶
User story: Como jogador, quero que o jogo termine ao colidir, para que haja consequência por erro.
Critérios de aceitação¶
- QUANDO o pássaro colide com uma coluna ou com o chão, O sistema DEVE transicionar para o estado GAME_OVER.
- QUANDO ocorre colisão, O sistema DEVE emitir partículas de bloco quebrando no ponto de impacto e tocar o som de dano.
- ENQUANTO em estado GAME_OVER, O sistema DEVE exibir tela de fim ("Game Over" estilizado, pontuação atual e recorde) e congelar o movimento dos obstáculos.
- QUANDO o jogador pressiona ESPAÇO, clica ou pressiona o botão A do controle na tela de GAME_OVER, O sistema DEVE reiniciar a partida no estado PRONTO.
- A detecção de colisão DEVE usar hitbox retangular do pássaro reduzida (~85% do sprite) para tolerância justa.
R4 — Pontuação¶
User story: Como jogador, quero acumular pontos e ver meu recorde, para medir meu progresso.
Critérios de aceitação¶
- QUANDO o pássaro ultrapassa completamente uma coluna, O sistema DEVE incrementar a pontuação em 1 ponto e tocar o som de ponto (estilo XP orb).
- ENQUANTO o jogo está em estado JOGANDO, O sistema DEVE exibir a pontuação atual no topo da tela em fonte pixelada.
- QUANDO a pontuação da partida em curso supera o recorde, O sistema DEVE atualizar o recorde e persisti-lo em arquivo local (
highscore.json) — sem esperar o fim da partida, para que o recorde sobreviva ao encerramento abrupto do app pelo sistema operacional (comum em Android). - QUANDO o jogo inicia, O sistema DEVE carregar o recorde do arquivo local; SE o arquivo não existir ou estiver corrompido, O sistema DEVE assumir recorde 0.
- O arquivo de recorde DEVE ser gravado em diretório com permissão de escrita garantida na plataforma (em Android, o armazenamento privado do app; no desktop rodando a partir do código-fonte, a raiz do projeto; no executável empacotado — Windows/Linux via PyInstaller, R13 —, a pasta onde o executável está, nunca o diretório temporário de extração), nunca dependendo do diretório de trabalho corrente.
R5 — Progressão de biomas¶
User story: Como jogador, quero que o cenário e a dificuldade evoluam conforme pontuo, para manter o jogo interessante.
Critérios de aceitação¶
- QUANDO a pontuação atinge 0, 10 e 25 pontos, O sistema DEVE ativar respectivamente os biomas Overworld, Cave e Nether.
- QUANDO um bioma é ativado, O sistema DEVE aplicar seus parâmetros: cor de fundo/decoração, texturas das colunas, velocidade de rolagem e tamanho da abertura.
- A dificuldade DEVE crescer por bioma: Overworld (velocidade 2.5, abertura 160 px), Cave (3.0, 145 px), Nether (3.5, 130 px) — valores de referência ajustáveis no design.
- QUANDO ocorre transição de bioma, O sistema DEVE fazer transição visual gradual do fundo (fade ≤ 1 s) e exibir o nome do bioma brevemente na tela.
R6 — Estados do jogo¶
User story: Como jogador, quero telas claras de início e fim, para entender o que fazer.
Critérios de aceitação¶
- QUANDO o jogo abre, O sistema DEVE exibir o estado PRONTO com título, pássaro flutuando (animação idle) e instrução de comando.
- QUANDO o jogador dá o primeiro comando de voo no estado PRONTO, O sistema DEVE transicionar para JOGANDO.
- QUANDO o jogador pressiona ESC, P ou o botão Start do controle durante JOGANDO, O sistema DEVE pausar/despausar o jogo com overlay "Pausado".
- Os estados válidos DEVEM ser exatamente: PRONTO, JOGANDO, PAUSADO, GAME_OVER, com transições PRONTO→JOGANDO, JOGANDO↔PAUSADO, JOGANDO→GAME_OVER, GAME_OVER→PRONTO.
R7 — Apresentação visual (temática Minecraft)¶
User story: Como jogador, quero visual voxel estilo Minecraft, para que a temática seja reconhecível.
Critérios de aceitação¶
- Todos os sprites DEVEM ser gerados por código em estilo pixel-art/voxel (blocos 16×16 escalados), sem uso de assets da Mojang.
- O pássaro DEVE ser uma abelha voxel (corpo amarelo/listras pretas, asas animadas em 2 frames).
- O chão DEVE ser uma faixa rolante de blocos do bioma atual, com rolagem sincronizada à velocidade das colunas.
- ENQUANTO em qualquer estado, O sistema DEVE renderizar decoração de fundo do bioma (Overworld: nuvens e colinas; Cave: estalactites e minérios; Nether: lava e fortaleza) com parallax.
- Os textos DEVEM usar fonte estilo pixelada com sombra dura, imitando a UI do Minecraft.
- A renderização de texto NÃO DEVE depender de fontes instaladas no sistema operacional — o resultado DEVE ser idêntico em desktop e Android (que não possui as fontes usadas na v1).
R8 — Áudio¶
User story: Como jogador, quero sons de feedback, para que as ações tenham resposta audível.
Critérios de aceitação¶
- QUANDO o pássaro voa, pontua, colide ou muda de bioma, O sistema DEVE tocar o som correspondente (flap, XP orb, dano/bloco quebrando, portal).
- Todos os sons DEVEM ser sintetizados por código (ondas quadradas/ruído, estilo 8-bit) — sem arquivos de áudio externos.
- QUANDO o jogador pressiona M, O sistema DEVE alternar mudo/som.
- SE o dispositivo de áudio não estiver disponível, O sistema DEVE continuar funcionando sem som (degradação graciosa).
R9 — Requisitos não funcionais¶
- O jogo DEVE rodar a 60 FPS fixos, com resolução lógica fixa de 480×720; a tela real pode ter qualquer proporção, preenchida por
pygame.SCALEDsem distorcer nem cortar a imagem — o excedente de um dos eixos vira barra (letterbox), nunca sobrando nas laterais graças à orientação travada em retrato (ver R14.3, R14.4). - O jogo DEVE depender apenas de Python ≥ 3.10 e
pygame-ce≥ 2.5 (mais stdlib) em tempo de execução.pygame-cesubstitui opygameusado na v1 por ser o pacote com suporte a Android na cadeia de build escolhida (R17) e ser compatível a nível de API. - O projeto DEVE ser gerenciado com a ferramenta
uv(instalada localmente): dependências declaradas empyproject.toml, ambiente criado viauv synce jogo iniciado comuv run main.pya partir da raiz do projeto. - A física DEVE ser determinística por frame (timestep fixo via clock do Pygame).
- O jogo DEVE manter 60 FPS em aparelho Android de entrada (referência: 4 núcleos, Android 8, sem GPU dedicada), medido pelo tempo de frame de
update+draw.
R10 — Controle de Xbox¶
User story: Como jogador, quero jogar com controle de Xbox, para ter alternativa ao teclado/mouse.
Critérios de aceitação¶
- QUANDO o jogo inicia, O sistema DEVE detectar controles conectados via
pygame.joysticke inicializá-los. - QUANDO um controle é conectado ou desconectado durante o jogo (hotplug), O sistema DEVE tratar o evento sem travar e atualizar a lista de controles ativos.
- Os mapeamentos DEVEM ser: botão A → voar/reiniciar, Start → pausar/despausar, botão Y → mudo, equivalentes às teclas de teclado.
- Teclado, mouse e controle DEVEM funcionar simultaneamente, sem necessidade de seleção de dispositivo.
- SE nenhum controle estiver conectado, O sistema DEVE funcionar normalmente com teclado/mouse.
R11 — Créditos¶
User story: Como criador do jogo, quero que meu nome e do meu parceiro apareçam no jogo, para receber crédito pela autoria.
Critérios de aceitação¶
- QUANDO o jogo exibe o estado PRONTO, O sistema DEVE exibir os nomes dos criadores ("Douglas e Pedro") abaixo do título.
- O título da janela (barra de título/taskbar) DEVE incluir os nomes dos criadores.
R12 — Recorde na tela inicial¶
User story: Como jogador, quero ver meu recorde atual assim que abro o jogo, para me sentir motivado a superá-lo.
Critérios de aceitação¶
- QUANDO o jogo exibe o estado PRONTO, O sistema DEVE exibir o recorde atual em destaque no rodapé da tela, acima do chão.
- SE não houver recorde registrado, O sistema DEVE exibir "RECORDE: 0".
R13 — Distribuição e empacotamento¶
User story: Como jogador sem Python instalado, quero baixar um executável e simplesmente dar duplo clique para jogar, sem precisar instalar nada.
Critérios de aceitação¶
- O projeto DEVE poder ser empacotado em um executável standalone (Windows e Linux) via PyInstaller, sem exigir Python ou
uvna máquina de destino. - QUANDO uma Release é publicada no GitHub com uma tag de versão, O sistema de CI DEVE gerar automaticamente os executáveis de Windows e Linux e anexá-los como assets da release, comprimidos como
.zip(Windows) e.tar.bz2(Linux). - A mesma Release DEVE também publicar o APK de Android (ver R17), totalizando três assets por release.
R14 — Compatibilidade com Android (celular e tablet, sempre em retrato)¶
User story: Como jogador, quero jogar no meu celular, para não depender de um PC.
Critérios de aceitação¶
- O jogo DEVE rodar em Android 5.0 (API 21) ou superior, em celulares e tablets, sempre em orientação retrato.
- O APK DEVE conter as ABIs
armeabi-v7a,arm64-v8aex86_64, para cobrir aparelhos antigos, atuais e ambientes x86 (emuladores, Chromebooks). - QUANDO o jogo inicia em qualquer aparelho, O sistema DEVE preencher a tela real sem distorcer a imagem, sem cortar UI e sem cortar nenhuma parte da imagem renderizada; barra (letterbox) no topo/base é aceitável. A resolução lógica DEVE continuar fixa em 480×720 em qualquer aparelho (a mesma calibração de dificuldade da task 12, nunca mudando com a tela). Com a orientação sempre travada em retrato (critério 4), a tela real nunca fica mais larga, relativamente, que a base 2:3 — a barra que sobra é sempre letterbox, nunca pillarbox (ver design.md seção 20.1).
- O manifesto do APK DEVE travar a orientação em retrato (
orientation = portraitnobuildozer.spec) — o jogo NUNCA DEVE rodar em paisagem no Android, mesmo que o aparelho seja fisicamente girado. - O jogo NÃO DEVE depender de fontes, arquivos ou recursos do sistema operacional que não existam no Android (ver R7.6).
- SE o aparelho não tiver saída de áudio disponível, O sistema DEVE continuar funcionando sem som (já coberto por R8.4, reafirmado para Android).
R15 — Entrada por toque e teclas do Android¶
User story: Como jogador de celular, quero comandar o jogo tocando na tela, e quero que os botões do aparelho façam o esperado.
Critérios de aceitação¶
- QUANDO o jogador toca em qualquer ponto da área de jogo, O sistema DEVE executar a ação de voar/iniciar/reiniciar — equivalente a ESPAÇO (R1.1, R3.4, R6.2).
- QUANDO o jogador aciona o botão BACK do Android durante JOGANDO, O sistema DEVE pausar o jogo (em vez de encerrar o app).
- QUANDO o jogador aciona o botão BACK do Android em PRONTO, PAUSADO ou GAME_OVER, O sistema DEVE encerrar o jogo.
- O sistema DEVE oferecer forma de alternar mudo sem teclado físico: um controle de mudo tocável na tela (celular), além do botão Y do gamepad já previsto em R10.3.
- Toque, teclado, mouse e controle DEVEM continuar funcionando simultaneamente, sem seleção de dispositivo (extensão de R10.4).
R16 — Ciclo de vida do aplicativo Android¶
User story: Como jogador de celular, quero que o jogo não me prejudique quando eu receber uma ligação ou trocar de app.
Critérios de aceitação¶
- QUANDO o app vai para segundo plano durante JOGANDO, O sistema DEVE transicionar automaticamente para PAUSADO.
- QUANDO o app retorna ao primeiro plano, O sistema DEVE permanecer em PAUSADO, aguardando comando explícito do jogador para retomar.
- O recorde DEVE ser persistido conforme R4.5 (armazenamento privado do app no Android).
- QUANDO o sistema operacional encerra o app sem aviso, o recorde já alcançado DEVE ser preservado (garantido pela gravação incremental de R4.3).
R17 — Empacotamento Android (APK)¶
User story: Como jogador, quero baixar um APK e instalar direto, sem loja de aplicativos e sem descompactar nada.
Critérios de aceitação¶
- O projeto DEVE poder ser empacotado como APK via Buildozer/python-for-android, assinado com chave de debug — suficiente para instalação direta ao permitir "fontes desconhecidas".
- QUANDO uma Release é publicada no GitHub com tag de versão, O sistema de CI DEVE gerar o APK e anexá-lo como asset da release sem compressão (arquivo
.apkpuro,BlockyBird-<tag>.apk), pronto para download e instalação direta. - O manifesto DEVE travar o app em orientação retrato (ver R14.4).
R18 — Conformidade com Ruff¶
User story: Como mantenedor, quero que todo o código Python do projeto siga um padrão de lint e formatação verificado por ferramenta, para reduzir bugs bobos e manter o estilo consistente conforme o projeto cresce.
Critérios de aceitação¶
- O projeto DEVE declarar
ruffcomo dependência de desenvolvimento empyproject.toml(grupodev), com versão mínima fixada. - O projeto DEVE ter configuração explícita de
ruff(seção[tool.ruff]empyproject.toml) definindo o conjunto de regras de lint habilitado e o comprimento de linha. - QUANDO
uv run ruff check .é executado a partir da raiz do projeto, O sistema DEVE reportar zero violações sobre todo o código Python versionado (src/,tests/,main.py,scripts/, receitas locais emp4a-recipes/). - QUANDO
uv run ruff format --check .é executado, O sistema DEVE reportar que nenhum arquivo precisa de reformatação. - O pipeline de CI (GitHub Actions) DEVE rodar
ruff checkeruff format --checkem cada push/pull request, falhando o job SE houver qualquer violação — para que uma regressão de lint não seja mesclada sem ser notada. - Violações encontradas na auditoria inicial DEVEM ser corrigidas no código (não silenciadas com
# noqagenérico); supressões pontuais DEVEM ser justificadas com um comentário curto quando a regra não se aplicar ao caso.
R19 — Tamanho de fonte sem sobreposição¶
User story: Como jogador, quero que todos os textos do jogo sejam legíveis e não se sobreponham entre si nem com outros elementos de UI, em qualquer tela do jogo.
Critérios de aceitação¶
- Em cada tela do jogo (PRONTO, HUD durante JOGANDO, PAUSADO, GAME_OVER), os retângulos ocupados por textos renderizados NÃO DEVEM se sobrepor entre si, nem com o chão, nem com o ícone de mudo (R15.4).
- Todo texto renderizado DEVE caber inteiramente dentro dos limites da resolução lógica (480×720), com uma margem mínima de 20 px nas laterais e sem ultrapassar o topo ou a área do chão.
- O espaçamento vertical entre linhas de texto de uma mesma tela DEVE ser calculado a partir da altura real do texto renderizado no tamanho escolhido (não um deslocamento fixo em pixels independente do tamanho da fonte), para que o layout continue correto se o tamanho for ajustado no futuro.
- Cada papel de texto (título, subtítulo/créditos, instrução, HUD de pontuação, overlay de pausa, textos de game over, recorde) DEVE ter um tamanho definido que priorize legibilidade sem violar os critérios 1–2 — o levantamento do tamanho ideal por papel é parte da implementação desta versão.
- A verificação de não sobreposição DEVE ser automatizada (teste que renderiza cada tela e compara os retângulos dos textos), para não depender de inspeção visual manual a cada mudança futura de texto ou fonte.
R20 — Conformidade com ty (checagem de tipos)¶
User story: Como mantenedor, quero que o código do jogo seja verificado por um checador de tipos estático, para detectar incompatibilidades de tipo (ex.: o bug de tuple[int, ...] vs. tuple[int, int, int] encontrado na auditoria inicial) antes que virem bug em produção.
Critérios de aceitação¶
- O projeto DEVE declarar
ty(Astral) como dependência de desenvolvimento empyproject.toml(grupodev), com versão mínima fixada. - O projeto DEVE ter configuração explícita de
ty([tool.ty.environment]/[tool.ty.src]empyproject.toml) definindo a versão-alvo do Python e os caminhos excluídos da checagem. - QUANDO
uv run ty check .é executado a partir da raiz do projeto, O sistema DEVE reportar zero diagnósticos sobre o código do jogo (src/,tests/,main.py,scripts/). - Módulos que só existem em tempo de execução em uma plataforma específica (ex.:
android.storage, disponível apenas dentro do runtime python-for-android) e código de teste que monkeypatcha atributos dinâmicos em objetos NÃO DEVEM ser tratados como violação — DEVEM ser suprimidos pontualmente com# ty: ignore[regra]e um comentário curto explicando o motivo, nunca com uma supressão genérica. - Receitas locais de build Android (
p4a-recipes/) DEVEM ficar fora do escopo da checagem de tipos — elas importam módulos (sh,pythonforandroid.*) que só existem dentro da imagem Docker do buildozer (R17), nunca no ambiente de desenvolvimento local. - O pipeline de CI (GitHub Actions) DEVE rodar
ty checkem cada push/pull request, falhando o job SE houver qualquer diagnóstico de erro — para que uma regressão de tipo não seja mesclada sem ser notada. - Violações reais encontradas na auditoria inicial DEVEM ser corrigidas no código (não suprimidas) sempre que a causa for um tipo genuinamente incompatível, não uma limitação do ambiente de checagem.
R21 — Ícone do aplicativo¶
User story: Como jogador, quero reconhecer o Blocky Bird pelo ícone na área de trabalho, na barra de tarefas e na tela inicial do Android, para identificar e abrir o jogo rapidamente entre os outros aplicativos instalados.
Critérios de aceitação¶
- O ícone do aplicativo DEVE ter a abelha voxel do jogo (R7.2,
textures.make_bee) como elemento central reconhecível, gerado por código a partir das mesmas texturas/paletas do jogo — sem imagem externa nem material de terceiros (R7.1). - No desktop, a janela do jogo (barra de título/taskbar) DEVE exibir o ícone da abelha em vez do ícone padrão do pygame, tanto rodando via
uv run main.pyquanto no executável empacotado (R13.1). - O executável Windows gerado por PyInstaller (
BlockyBird.spec, R13.1) DEVE embutir o ícone da abelha como ícone do arquivo.exe, visível no Explorer e na barra de tarefas antes mesmo de o jogo abrir. - O APK Android (R17) DEVE declarar o ícone da abelha como ícone do launcher em formato de ícone adaptativo (camadas de primeiro plano e de fundo separadas), para que o sistema componha a forma final (círculo, "squircle", quadrado arredondado etc.) sem cortar nem distorcer a personagem, em qualquer aparelho Android 8.0+ (API 26+).
- Na camada de primeiro plano do ícone adaptativo, a abelha DEVE ficar inteiramente dentro da zona seguro-de-máscara (os 66 dp centrais de um canvas de 108 dp, ~61% da área), para não ser cortada por nenhuma máscara padrão do sistema — este é o comportamento concreto por trás de "aparecer com as proporções ajustadas".
- O APK Android TAMBÉM DEVE declarar um ícone legado (não adaptativo) equivalente, para aparelhos com Android anterior à 8.0 (API < 26), que não suportam ícone adaptativo.
- A geração de todas as variações/resoluções do ícone (desktop
.ico, ícone legado do Android, camadas adaptativas de primeiro plano/fundo) DEVE ser reprodutível por um script versionado no repositório — sem edição manual de imagem fora do controle de versão (scripts/generate_app_icon.py).
Fora de escopo¶
Multiplayer, skins alternativas, música de fundo contínua, menus de configuração, salvamento em nuvem, publicação na Google Play Store (o APK é para instalação direta; assinatura de release com keystore próprio fica para uma versão futura), suporte a iOS. Ícone temático/monocromático do Android 13+ ("Material You" themed icons) e imagem de destaque ("feature graphic") de loja de aplicativos ficam fora de escopo — dependem de publicação na Play Store, já fora de escopo.