MTG Conquest — Life Tracker (Flutter)

6 minutos de leitura

Este post serve como changelog oficial do app. Sempre que uma nova versão for lançada, adicione uma entrada no topo da seção de changelog abaixo.

⬇ Baixar APK — v1.0.0

Instale em qualquer Android habilitando “Fontes desconhecidas” nas configurações.


Changelog

[Unreleased] — 19/02/2026

  • Bug fix: Dano de comandante — ao reduzir o contador com -, a vida do jogador afetado não é restaurada. Correção aplica o delta real (após clamp 0–99) nos dois sentidos dentro do método addCommanderDamage, sem necessidade de método novo.
  • Nova funcionalidade: StormCounter na TopBar, ao lado do relógio de partida
    • Um único botão global — qualquer jogador toca para incrementar o contador a cada magia conjurada na mesa
    • Reseta manualmente (long-press) ou automaticamente ao passar o turno do jogador ativo
  • Nova funcionalidade: Marcadores de jogador do Magic na PlayerArea:
    • Poison (veneno) - jogador perde com 10 counters
    • Energy (energia) - recurso persistente entre turnos
    • Experience (experiência) - acumula durante a partida
    • Rad (radiação do set Fallout) - representa níveis de radiação
    • Ticket (do set Unfinity) - usado para mecânicas de sticker
    • Implementar PlayerCounters widget com grid de botões +/-
    • Adicionar campo de marcadores no PlayerModel

v1.0.0 — 18/02/2026

  • Lançamento inicial
  • Suporte a 1–4 jogadores com layouts dedicados e rotação 180° para modo mesa
  • Rastreamento de vida (toque ±1, long-press ±5) e dano de comandante por slot (main/partner)
  • Suporte a partners: até 2 comandantes por jogador
  • Eliminação automática ao zerar vida (sem confirmação) e por dano de comandante (com confirmação)
  • Timers de partida (countdown configurável) e turno (count-up com acúmulo por jogador)
  • Busca de comandantes via API Scryfall com validação de legalidade e identidade de cor
  • Indicador de land por jogador: ativo apenas para o jogador do turno, reseta automaticamente ao passar o turno
  • Fase pré-início com scry, timers pausados e sorteio de jogador inicial
  • Histórico de partidas em memória (vencedor, comandante, turno, tempo)
  • Regras de Conquest: eliminação por vida zerada ou dano de comandante acumulado ≥ limite

Sobre o App

App Flutter para partidas de Commander no formato Conquest. Offline, sem persistência entre sessões, estado reativo via Riverpod. Todos os dados vivem em memória enquanto o app está aberto.

Funcionalidades

  • 1–4 jogadores com layouts dedicados e rotação 180° para modo mesa
  • Vida com toque/long-press (±1/±5) por área do jogador
  • Dano de comandante rastreado individualmente por slot (main e partner separados)
  • Suporte a partners: até 2 comandantes por jogador
  • Eliminação por vida zerada: automática e imediata, sem confirmação
  • Eliminação por dano de comandante: exibe dialog de confirmação antes de eliminar
  • Badge com maior dano recebido e nome do comandante ameaçador
  • Indicador de land play por turno (só jogador ativo)
  • Regra de scry pela distância circular ao jogador inicial
  • Fase pré-início: scry visível, timers pausados, sorteio de início
  • Countdown de partida configurável + count-up acumulado por turno/jogador
  • Menu in-game (⋮) com reiniciar e voltar ao menu
  • Reiniciar mantém nomes e comandantes, zera vida e dano, sorteia novo início
  • Busca de comandantes via API Scryfall com identidade de cor e validação de legalidade
  • Background desfocado com arte do comandante por jogador
  • Configurações editáveis: vida inicial, limite de dano de comandante, deck mínimo, duração
  • Histórico em memória: vencedor, comandante, jogadores, turno, tempo

Arquitetura

O app segue arquitetura reativa com Riverpod como gerenciador de estado. A navegação principal é reativa: nenhuma tela faz Navigator.push no fluxo principal — o _AppRouter observa o gameProvider e renderiza a tela certa automaticamente. A HistoryScreen é a única exceção, aberta via Navigator.push como overlay.

Models

Model Responsabilidade
GameState Snapshot imutável da partida: jogadores, config, activePlayerIndex, startingPlayerIndex, turnNumber, isStarted, isGameOver, winnerId
GameConfig Configurações: playerCount, vida inicial, limite de dano de comandante, deck mínimo, duração da partida, tableMode, startingPlayerIndex (índice inicial antes de qualquer sorteio)
PlayerModel Dados do jogador: vida, commanders (até 2), dano recebido por chave "sourcePlayerId_slot", isEliminated, landPlayedThisTurn, turnTimeAccumulated
CommanderModel Nome, imageUrl (nullable — null quando adicionado manualmente sem busca Scryfall) e colorIdentity
MatchRecord Registro imutável de partida encerrada: vencedor, comandante, jogadores, turno, tempo decorrido

Providers

GameNotifierStateNotifier<GameState?> central. Estado null significa que o app está na tela de setup.

Principais métodos:

  • startGame — Inicializa jogadores com vida cheia usando config.startingPlayerIndex como ponto de partida; entra em fase pré-início com timers pausados (o sorteio do jogador inicial é feito depois, por randomizeStartingPlayer)
  • startMatch — Define isStarted: true e inicia os dois timers (partida e turno)
  • restartGame — Reseta vida e dano, preserva nomes e comandantes, sorteia novo início, volta ao pré-início
  • resetGame — Para timers e define state = null, voltando ao SetupScreen
  • adjustLife — Soma delta à vida do jogador; se vida ≤ 0, elimina automaticamente sem confirmação
  • eliminatePlayer — Marca jogador como eliminado e verifica condição de vitória
  • nextTurn — Acumula tempo do turno no jogador ativo, reseta land indicator, avança ao próximo jogador vivo, reinicia o timer de turno
  • addCommanderDamage — Atualiza o contador "sourcePlayerId_slot" (clamp 0–99); se delta > 0, também desconta vida do alvo via adjustLife; se delta < 0, apenas corrige o contador (vida não é restaurada — bug conhecido)
  • setCommander — Define ou substitui comandante no slot 0 (main) ou 1 (partner)
  • removeCommander — Remove o comandante de um slot; dano já registrado na chave desse slot permanece
  • randomizeStartingPlayer — Sorteia novo jogador inicial durante fase pré-início (sempre diferente do atual)
  • getScryValue — Retorna distância circular do jogador ao startingPlayerIndex para cálculo do scry
  • toggleLandPlayed — Inverte landPlayedThisTurn; restrito ao jogador ativo, ignorado silenciosamente para os demais

MatchTimerNotifier — Countdown da duração da partida. Congelado no pré-início, parado automaticamente no game over.

TurnTimerNotifier — Count-up do turno atual. Reiniciado do zero a cada nextTurn, parado no game over.

HistoryNotifier — Lista em memória de MatchRecord, mais recente primeiro. Alimentado automaticamente ao fim de cada partida. Suporta clearHistory().

Telas

Tela Descrição
SetupScreen Configuração de nomes, comandantes (main + partner por jogador) e GameConfig. Acesso ao histórico via overlay
GameScreen Layout adaptativo 1–4 jogadores com rotação 180° no modo mesa e TopBar fixo
GameOverScreen Exibe vencedor, comandante(s), vida final e tempo acumulado de cada jogador. Botões Reiniciar e Voltar ao Menu
HistoryScreen Lista de partidas registradas com opção de limpar histórico completo

Widgets Principais

Widget Descrição
TopBar Pré-início: ícone de dado (Sortear) + timer estático + botão Iniciar. In-game: menu ⋮ + turno/jogador ativo + countdown com cor dinâmica + botão Passar Turno com tempo do turno
PlayerArea Background desfocado com arte do comandante, LifeCounter, botões ±1/±5, indicadores de comandante, badge de maior dano, LandIndicator, ScryIndicator
CommanderDamagePanel Bottom sheet com dano por fonte (main e partner separados). Botões ±. Ao atingir o limite, exibe dialog de confirmação antes de eliminar. Rodapé exibe “Maior dano único / limite” do jogador alvo
CommanderSearch Busca Scryfall com debounce de 500 ms, até 5 sugestões, preview de arte, identidade de cor e validação de legalidade (Legendary Creature ou Legendary Planeswalker, commander: legal)
LifeCounter Total de vida com estilo adaptado a valores negativos ou de muitos dígitos
ScryIndicator Badge com valor de scry (distância ao jogador inicial). Visível apenas na fase pré-início
LandIndicator Toggle de land play. Clicável apenas pelo jogador ativo; resetado a cada nextTurn

Serviços

ScryfallService — Dois endpoints: autocomplete(query) para sugestões de nomes e fetchByName(name) para busca completa com validação de tipo e legalidade.

Roteamento

_AppRouter — Widget reativo que observa gameProvider e renderiza SetupScreen (state null), GameScreen (state não-null, isGameOver: false) ou GameOverScreen (isGameOver: true). Histórico usa Navigator.push como overlay sobre o fluxo principal.