Flappy Dird: Flappy Bird implementado no Finder do macOS
(eieio.games)- Flappy Dird é uma implementação experimental que executa Flappy Bird usando a lista de arquivos do Finder do macOS como tela e as ações de selecionar e abrir arquivos como entrada
- A ideia central surgiu de ler como um botão o valor Date Last Opened de um diretório, que só é atualizado no Finder, e de desenhar a tela alterando nomes de arquivos de links simbólicos
- A renderização simples por mudança de nomes de arquivos era lenta e sofria muito com tearing, então foi melhorada com uma técnica de double buffering que usa AppleScript para trocar o diretório-alvo da janela do Finder
- A entrada por duplo clique era instável acima de 2 FPS devido à limitação de timestamps em segundos, e depois foi substituída por um método que lê os itens selecionados no Finder
- A estrutura final usa AppleScript no loop e Python para o estado do jogo e a renderização; roda a 4 FPS, mas ainda tem entradas perdidas e limitações típicas do Finder
Usando o Finder como tela de jogo
- Flappy Dird é um jogo baseado no Finder cujo código pode ser visto e executado no repositório do GitHub
- O jogo inclui instruções de uso, acompanhamento da maior pontuação e um banner publicitário rolante
- O usuário inicia o jogo com um duplo clique e faz o pássaro pular selecionando qualquer arquivo dentro da janela do Finder
- A velocidade de execução fica em torno de 4 quadros por segundo; é difícil rodar muito mais rápido que isso, e a entrada também falha ocasionalmente
Usando Date Last Opened como um botão
- O ponto de partida foi a existência, no Finder, de um campo Date Last Opened para diretórios
- Foram observados três comportamentos
- Entrar no diretório com
cdnão atualiza o timestamp - Dar duplo clique no Finder em um link simbólico para o diretório atualiza o timestamp
- É possível ler esse valor com precisão de segundos usando
mdls
- Entrar no diretório com
- Usando essa característica, foi possível criar um botão dentro do Finder
- Criar, dentro do diretório
dir, um link simbólicobuttonapontando paradir - Ler, no início, o último horário em que
dirfoi aberto - Consultar repetidamente o timestamp e tratar como entrada quando o valor mudar
- Abrir
buttonsem mudar a localização do Finder para alterar o último horário em quedirfoi aberto
- Criar, dentro do diretório
Desenhando uma tela 15x15 com nomes de arquivos em emoji
- Era difícil alinhar arte ASCII com a fonte padrão do Finder, mas foi aproveitado o fato de que emojis inseridos nos nomes de arquivos aparecem com largura constante
- O protótipo criava, dentro de
dir, 15 links simbólicos filhos que apontavam novamente paradir, e compunha a tela renomeando cada link como uma linha de emojis - A função de renderização recebe os valores
bird_y_pos,pipe_locationseframee cria uma grade de emojis 15x15 - A cada quadro, todos os links simbólicos dentro do diretório são renomeados para as respectivas linhas da grade de emojis
- Também foi necessário um hack para ajustar a ordem de renomeação de modo que o Finder ordenasse por
Date Modified - Essa abordagem funcionava, mas era muito lenta e sofria muito com tearing
AppleScript e double buffering
- Para reduzir o tearing, foi procurada uma forma de atualizar a lista de arquivos do Finder, usando o comando AppleScript
tell application "Finder" to tell front window to update every item - Esse comando ajudou um pouco, mas o tearing continuou, o que levou a controlar o Finder diretamente por AppleScript
- Havia várias possibilidades para double buffering
- O método de trocar atomicamente o conteúdo do diretório com links simbólicos não servia, porque o Finder não conseguia expandir o conteúdo de diretórios via link simbólico
- O método de usar dois diretórios que apontam um para o outro podia reduzir o tearing, mas só avançava o quadro quando o usuário clicava, então não era adequado para o jogo
- Era necessário um método para mudar o diretório exibido pelo Finder sem alterar o “último horário de abertura”
- A solução foi mudar o diretório-alvo da janela do Finder com
tell application "Finder" to set target of front Finder window to ("PATH" POSIX file)no AppleScript - Com isso, o double buffering foi implementado e o jogo passou a rodar de forma suave a 1 FPS, mas ainda era lento
Mudança no método de entrada: de duplo clique para seleção de arquivo
- A entrada por duplo clique tinha limites acima de 2 FPS porque a precisão do timestamp era em segundos
- Como era possível ler os itens selecionados na janela do Finder com AppleScript, a entrada foi alterada para “selecionar qualquer arquivo”
- O novo loop de jogo tinha a seguinte estrutura
- Esperar até o usuário dar duplo clique
- A cada quadro, verificar via AppleScript se há algum arquivo selecionado na janela atual
- Se houver seleção ou se o último horário de abertura tiver mudado, o pássaro pula
- Mudar via AppleScript o diretório exibido pelo Finder
- Esperar pelo tempo restante para atingir a taxa de quadros-alvo
- Esse método chegou razoavelmente a 2 FPS, mas cada chamada AppleScript levava cerca de 0,2 segundo, um custo fixo alto
- Mesmo a 2 FPS, se o usuário selecionasse um arquivo depois da verificação de entrada, a entrada podia ser perdida
Movendo o loop principal para AppleScript
- O profiling mostrou que um AppleScript que apenas imprimia o número
1no log e encerrava levava cerca de 0,14 segundo - Depois, foi acrescentada uma correção explicando que essa não era uma medição justa do tempo de inicialização do AppleScript, e que, considerando um AppleScript vazio, o valor poderia estar mais perto de 0,06 segundo
- Para evitar pagar o custo de inicialização do AppleScript a cada quadro, o loop principal foi movido para AppleScript, enquanto Python ficou responsável pela lógica do jogo e pela renderização
- A estrutura básica é a seguinte
game.py awaitespera o duplo clique inicial do usuáriogame.py start-frameregistra o horário de início do quadro- Lê o número de itens selecionados no Finder, passa para
game.py ticke renderiza o quadro - Recebe o nome do diretório preparado por
ticke muda o alvo da janela do Finder para esse diretório game.py sleepajusta a taxa de quadros-alvo, e o loop se repete se o jogador não tiver perdido
- Depois,
start-frameesleeppuderam ser combinados, e foi adicionado um loop para reiniciar após a morte
Detalhes necessários para finalizar o jogo
- O estado do jogo é armazenado em um arquivo
state.json, lido e escrito a cada quadro - Como a largura dos caracteres de texto usados era menor que a dos emojis, foram usados muitos espaçamentos hardcoded para manter o alinhamento
- O texto do banner rolante no topo foi especialmente complicado, e foi criada a função
read_n_ad_charspara estimar quantos caracteres mostrar por vez - Em vez de passar o diretório de trabalho para o AppleScript, variáveis de template são substituídas na chamada Python
first-time-setup - Como o AppleScript engolia a saída do Python, os logs eram adicionados a um arquivo e depois verificados com
cat - Para melhorar a sensação do jogo, tocar em dois quadros consecutivos faz o pássaro pular uma linha extra para cima
Limitações de um jogo do Finder feito sem engine
- O protótipo tinha cerca de 90 linhas, e o código final tem cerca de 550 linhas, mas aproximadamente um terço disso é boilerplate ou constantes
- A abordagem de guardar o estado dos quadros em uma pequena matriz 2D sem engine de jogo facilitou manter o jogo inteiro na cabeça
- 4 FPS e a entrada limitada são grandes restrições, mas parece possível criar outros jogos com a mesma abordagem
- A ideia de fazer Tetris no Finder também surgiu, e fica como um exemplo difícil, mas talvez possível
1 comentários
Comentários no Hacker News
Fiquei impressionado a ponto de ficar sem palavras e, mesmo me considerando alguém que pensa fora da caixa, antes de ler este post eu provavelmente teria dito que era impossível
É muito bom ver alguém levar adiante um projeto feito para se desafiar no fim de semana e descobrir se ele era de fato possível, e a leitura também foi muito divertida
Bob pega às pressas a jaqueta e a mochila para acompanhar a equipe que vai tomar cerveja na sexta à tarde e não percebe que deixou Flappy Bird rodando no MacOS
Durante o fim de semana prolongado pelo feriado bancário, a vida útil de escrita do SSD do notebook é consumida a 4 quadros por segundo, e na terça-feira Bob volta e descobre que o SSD não funciona mais
Gosto muito mais da abordagem “isto é uma imagem. agora não sei como incorporar tweets”
Fico curioso se o OP conseguiria resolver o problema de velocidade de inicialização do AppleScript reescrevendo em JavaScript
Há alguns anos, o framework padrão e a ferramenta de linha de comando
osascriptdão suporte quase equivalente a AppleScript e JavaScript, e no JavaScript há bastante coisa dentro do objeto$Para chamar eventos AppleScript de forma nativa em Python, também dá para experimentar o py-appscript
https://github.com/hhas/appscript/tree/master/py-appscript
Só usei a versão Ruby do Appscript, mas, se a versão Python funcionar do mesmo jeito, parece uma escolha ideal a ponto de dispensar o próprio AppleScript
Se você gosta desses jogos em lugares onde eles não deveriam existir, há projetos ainda mais absurdos
Fontemon: um jogo dentro de uma fonte https://www.coderelay.io/fontemon.html
Dungeons & Directories: uma aventura em texto dentro de um navegador de arquivos https://wheybags.com/dungeons_and_directories/ — este fui eu que fiz
Também mexi um pouco com o jogo na fonte e criei o Hexagone[1] para ver como era escrever uma fonte. Não cheguei a ter certeza de que jogo fazer, mas é divertido ter esse tipo de inspiração
[1] http://eieio.games/nonsense/hexagone-converting-hex-to-rgb-w...
https://github.com/facundoolano/rpg-cli
Gosto muito da atitude de “dá para fazer melhor” que permeia o texto inteiro. Fico imaginando como seria se todos os desenvolvedores tivessem essa mentalidade
Claro, se todo mundo fosse assim, nada jamais terminaria, porque sempre dá para melhorar, e às vezes é preciso alguém que diga “isso já está bom o suficiente”
Em projetos de hobby como este, é mais fácil manter o lado do “dá para fazer melhor”. No trabalho, normalmente acabamos pensando se o valor de gastar mais um dia refinando é maior do que o de todas as outras coisas que queremos fazer
Mas, no Flappy Dird, eu queria continuar até ficar satisfeito com o projeto ou até acreditar que não dava mais para levar o jogo adiante
Pelo título, no começo achei que seria uma abordagem de mover ícones como sprites com AppleScript na visualização por ícones de uma janela do Finder
Na verdade, foi implementado na visualização em lista, e no início usando polling da “data da última abertura” do diretório, o que me surpreendeu; é certamente uma abordagem criativa
É muito divertido o espírito deste texto de colocar joguinhos em todos os lugares possíveis
Isso me lembra o widget de barra de tarefas Fortune Teller fish que dava para usar no GNOME antigo
Separadamente, ao ver a capitalização “MacOS Finder”, primeiro achei que fosse uma extensão para MacOS 8 ou 9, mas o resultado real foi muito mais divertido
O próximo desafio é rodar o Jogo da Vida dentro do Finder e, dentro dele, emular um Mac que esteja rodando o Finder
Primeiro, você cria pastas conforme o padrão e clica com o botão direito para ordená-las, fazendo-as cair numa grade. Depois, um script lê as posições das pastas no arquivo DS_Store e atualiza para as novas posições do próximo quadro, ou cria e apaga pastas se necessário