Flappy Dird: Flappy Bird implementado en Finder de macOS
(eieio.games)- Flappy Dird es una implementación experimental que ejecuta Flappy Bird usando la lista de archivos de Finder de macOS como pantalla, y las acciones de seleccionar y abrir archivos como entrada
- La idea central parte de leer como si fuera un botón el valor Date Last Opened de un directorio, que solo se actualiza en Finder, y de dibujar la pantalla cambiando los nombres de archivos de enlaces simbólicos
- El renderizado mediante simples cambios de nombre de archivo era lento y sufría mucho tearing, por lo que se mejoró con un esquema de doble búfer que cambia el directorio objetivo de la ventana de Finder mediante AppleScript
- La entrada por doble clic era inestable por encima de 2 FPS debido al límite de timestamps en segundos, y luego se cambió a un método que lee los elementos seleccionados en Finder
- La estructura final usa AppleScript para ejecutar el loop y Python para encargarse del estado del juego y el renderizado; funciona a 4 FPS, pero aún tiene pérdidas de entrada y restricciones propias de Finder
Usar Finder como pantalla de juego
- Flappy Dird es un juego basado en Finder cuyo código puede revisarse y ejecutarse desde el repositorio de GitHub
- El juego incluye instrucciones de uso, seguimiento de puntaje máximo y anuncios tipo banner con desplazamiento
- El usuario inicia el juego con un doble clic y hace saltar al pájaro seleccionando cualquier archivo dentro de la ventana de Finder
- La velocidad de ejecución es de alrededor de 4 cuadros por segundo; es difícil hacerlo funcionar mucho más rápido y, a veces, también se pierden entradas
Usar Date Last Opened como botón
- El punto de partida fue que Finder tiene un campo Date Last Opened para directorios
- Se observaron tres comportamientos
- Entrar al directorio con
cdno actualiza el timestamp - Hacer doble clic en Finder sobre un enlace simbólico al directorio actualiza el timestamp
- Con
mdlsse puede leer ese valor con precisión de segundos
- Entrar al directorio con
- Aprovechando esta característica, fue posible crear un botón dentro de Finder
- Dentro del directorio
dir, se crea un enlace simbólicobuttonque apunta adir - Al iniciar, se lee la última hora de apertura de
dir - Se hace polling repetidamente del timestamp y, si el valor cambia, se trata como una entrada
- Se abre
buttonsin cambiar la ubicación de Finder, modificando así la última hora de apertura dedir
- Dentro del directorio
Dibujar una pantalla de 15x15 con nombres de archivo en emoji
- Era difícil alinear arte ASCII con la fuente predeterminada de Finder, pero se aprovechó que los emoji puestos en nombres de archivo se muestran con un ancho constante
- El prototipo creaba dentro de
dir15 subenlaces simbólicos que apuntaban nuevamente adir, y formaba la pantalla cambiando el nombre de cada enlace por una fila de emoji - La función de renderizado recibe
bird_y_pos,pipe_locationsyframe, y genera una cuadrícula de emoji de 15x15 - En cada cuadro, cambia el nombre de todos los enlaces simbólicos del directorio por cada fila de la cuadrícula de emoji
- También hizo falta un hack para ajustar el orden de los cambios de nombre de modo que Finder ordenara por
Date Modified - Este método funcionaba, pero era muy lento y tenía mucho tearing
AppleScript y doble búfer
- Para reducir el tearing se buscó una forma de refrescar la lista de archivos de Finder, y se usó el comando de AppleScript
tell application "Finder" to tell front window to update every item - Este comando ayudó un poco, pero el tearing persistía, y luego el enfoque derivó en controlar Finder directamente con AppleScript
- Hubo varios candidatos para el doble búfer
- El método de reemplazar atómicamente el contenido del directorio con enlaces simbólicos no servía, porque Finder no puede desplegar el contenido de un directorio de enlace simbólico
- Usar dos directorios que se apuntan entre sí podía reducir el tearing, pero solo permitía avanzar de cuadro cuando el usuario hacía clic, así que no era adecuado para el juego
- Hacía falta un método para cambiar el directorio que Finder muestra sin modificar la “última hora de apertura”
- La solución fue cambiar el directorio objetivo de la ventana de Finder con
tell application "Finder" to set target of front Finder window to ("PATH" POSIX file)en AppleScript - Al implementar el doble búfer con este método, el juego funcionó de forma fluida a 1 FPS, aunque seguía siendo lento
Cambio de método de entrada: de doble clic a selección de archivos
- La entrada por doble clic tenía un límite por la precisión de timestamps en segundos, así que por encima de 2 FPS resultaba problemática
- Como AppleScript permite leer los elementos seleccionados de la ventana de Finder, la entrada se cambió a “seleccionar cualquier archivo”
- El nuevo loop de juego tenía la siguiente estructura
- Esperar hasta que el usuario haga doble clic
- En cada cuadro, comprobar con AppleScript si hay algún archivo seleccionado en la ventana actual
- Si hay una selección o si cambió la última hora de apertura, el pájaro salta
- Cambiar con AppleScript el directorio que muestra Finder
- Esperar el tiempo restante para mantener la tasa de cuadros objetivo
- Este método logró llegar en cierta medida a 2 FPS, pero cada llamada a AppleScript tardaba aproximadamente 0.2 segundos, un costo fijo alto
- Incluso a 2 FPS, si el usuario seleccionaba un archivo después de la comprobación de entrada, la entrada podía perderse
Mover el loop principal a AppleScript
- El profiling mostró que un AppleScript que imprime el número
1en el log y termina tardaba aproximadamente 0.14 segundos - Más tarde se agregó una corrección: esa no era una medición justa del tiempo de arranque de AppleScript, y con un AppleScript vacío podría estar más cerca de 0.06 segundos
- Para evitar pagar el costo de iniciar AppleScript en cada cuadro, se movió el loop principal a AppleScript y se dejó a Python a cargo de la lógica del juego y el renderizado
- La estructura básica es la siguiente
game.py awaitespera el doble clic inicial del usuariogame.py start-frameregistra la hora de inicio del cuadro- Se lee la cantidad de elementos seleccionados en Finder, se pasa a
game.py ticky se renderiza el cuadro - Se recibe el nombre del directorio preparado por
ticky se cambia a ese directorio el objetivo de la ventana de Finder game.py sleepajusta la tasa de cuadros objetivo y, si el jugador no perdió, se repite
- Después,
start-frameysleeppudieron combinarse, y se agregó un loop para reiniciar tras morir
Detalles necesarios para completar el juego
- El estado del juego se guarda en un archivo
state.json, que se lee y escribe en cada cuadro - Como los caracteres de texto usados son más angostos que los emoji, se usaron muchos espaciados hardcodeados para alinear todo
- El texto del banner superior con desplazamiento fue especialmente complicado, y se creó una función
read_n_ad_charspara estimar cuántos caracteres mostrar a la vez - En lugar de pasar el directorio de trabajo a AppleScript, la llamada de Python
first-time-setupreemplaza variables de plantilla - Como AppleScript se tragaba la salida de Python, los logs se agregaban a un archivo y luego se revisaban con
cat - Se mejoró la sensación del juego haciendo que tocar en dos cuadros consecutivos haga saltar una línea más arriba
Limitaciones de un juego de Finder hecho sin motor
- El prototipo tenía unas 90 líneas, y el código final unas 550 líneas, aunque cerca de un tercio son boilerplate o constantes
- El enfoque de guardar el estado de cada cuadro en un pequeño arreglo 2D, sin usar un motor de juegos, facilitó mantener todo el juego en la cabeza
- Los 4 FPS y la entrada limitada son restricciones importantes, pero parece posible crear otros juegos con el mismo método
- También surgió la idea de hacer Tetris en Finder, que queda como un ejemplo difícil pero posiblemente viable
1 comentarios
Opiniones en Hacker News
Me dejó sin palabras de lo sorprendente que fue y, aunque me consideraba alguien que piensa fuera de la caja, antes de leer esto probablemente habría dicho que era imposible.
Me encanta ver cómo alguien lleva al límite un proyecto hecho para desafiarse durante el fin de semana y comprobar si realmente es posible; además, el texto fue muy entretenido de leer.
Bob, apurado por alcanzar al equipo que iba por unas cervezas el viernes por la tarde, tomó rápido su chaqueta y su mochila, y no se dio cuenta de que había dejado abierto Flappy Bird corriendo en macOS.
Durante el fin de semana largo con feriado bancario, la vida útil de escritura del SSD de su laptop se fue consumiendo a 4 cuadros por segundo, y cuando Bob volvió el martes descubrió que el SSD ya no funcionaba.
La solución de “esto es una imagen. Ahora ya no sé cómo embeber un tuit” me gusta mucho más, de hecho.
Me pregunto si el OP podría resolver el problema de la velocidad de arranque de AppleScript reescribiéndolo en JavaScript.
Desde hace algunos años, tanto el framework base como la herramienta de línea de comandos
osascriptsoportan AppleScript y JavaScript casi al mismo nivel, y en JavaScript hay bastantes cosas dentro del objeto$.Para llamar eventos de AppleScript de forma nativa desde Python, también se puede probar py-appscript.
https://github.com/hhas/appscript/tree/master/py-appscript
Solo he usado la versión de Appscript para Ruby, pero si la versión de Python funciona de la misma manera, parece una opción ideal hasta el punto de que ya no haría falta AppleScript en sí.
Si te gustan estos juegos en lugares donde no deberían estar, hay proyectos todavía más absurdos.
Fontemon: un juego dentro de una fuente https://www.coderelay.io/fontemon.html
Dungeons & Directories: una aventura de texto dentro de un navegador de archivos https://wheybags.com/dungeons_and_directories/ — este lo hice yo.
También jugué un poco con el juego de la fuente, e hice Hexagone[1] para ver qué se siente escribir una fuente. No me dio una idea clara de qué juego hacer, pero es divertido que surja este tipo de inspiración.
[1] http://eieio.games/nonsense/hexagone-converting-hex-to-rgb-w...
https://github.com/facundoolano/rpg-cli
Me encanta la actitud de “se puede hacer mejor” que atraviesa todo el artículo. Me pregunto cómo sería si todos los desarrolladores tuvieran esa mentalidad.
Claro que, si todos fueran así, como siempre se puede mejorar, nunca se terminaría nada, y a veces hace falta alguien que diga: “con esto alcanza”.
En proyectos personales de este tipo es más fácil mantenerse del lado de “se puede hacer mejor”. En el trabajo, normalmente uno piensa si vale la pena invertir un día más en pulir algo frente a todo lo demás que queremos hacer.
Pero con Flappy Dird quería seguir hasta estar satisfecho con el proyecto, o hasta convencerme de que ya no podía llevar el juego más lejos.
Al ver el título, al principio pensé que sería una implementación que movía íconos como sprites con AppleScript en la vista de íconos de una ventana del Finder.
Me sorprendió que en realidad estuviera implementado en la vista de lista y que al principio funcionara haciendo polling de la “fecha de última apertura” del directorio; sin duda es un enfoque creativo.
Me divierte mucho el espíritu de este artículo de llevar jueguitos a todos los lugares posibles.
Me hizo acordar al widget de barra de tareas Fortune Teller fish que se podía usar en el viejo GNOME.
Por otro lado, al ver la capitalización “MacOS Finder” al principio pensé que era una extensión para MacOS 8 o 9, pero el resultado real fue mucho más divertido.
El próximo desafío es ejecutar el Juego de la vida dentro de Finder y, dentro de eso, emular una Mac que ejecute Finder.
Primero se crean carpetas según el patrón y se ordenan con clic derecho para que queden en una cuadrícula. Luego, un script lee las posiciones de las carpetas desde el archivo DS_Store y las actualiza a las nuevas posiciones del siguiente cuadro, o crea y elimina carpetas si hace falta.