1 puntos por GN⁺ 2023-10-09 | 1 comentarios | Compartir por WhatsApp
  • 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 cd no actualiza el timestamp
    • Hacer doble clic en Finder sobre un enlace simbólico al directorio actualiza el timestamp
    • Con mdls se puede leer ese valor con precisión de segundos
  • Aprovechando esta característica, fue posible crear un botón dentro de Finder
    • Dentro del directorio dir, se crea un enlace simbólico button que apunta a dir
    • 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 button sin cambiar la ubicación de Finder, modificando así la última hora de apertura de dir

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 dir 15 subenlaces simbólicos que apuntaban nuevamente a dir, 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_locations y frame, 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 1 en 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 await espera el doble clic inicial del usuario
    • game.py start-frame registra la hora de inicio del cuadro
    • Se lee la cantidad de elementos seleccionados en Finder, se pasa a game.py tick y se renderiza el cuadro
    • Se recibe el nombre del directorio preparado por tick y se cambia a ese directorio el objetivo de la ventana de Finder
    • game.py sleep ajusta la tasa de cuadros objetivo y, si el jugador no perdió, se repite
  • Después, start-frame y sleep pudieron 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_chars para estimar cuántos caracteres mostrar a la vez
  • En lugar de pasar el directorio de trabajo a AppleScript, la llamada de Python first-time-setup reemplaza 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

 
GN⁺ 2023-10-09
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.

    • Por eso en software nunca digo que algo es imposible. Solo hacen falta tiempo y creatividad.
  • 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.

    • Me pregunto si alguien capaz de correr Flappy Dird en la empresa realmente tendría tanta probabilidad de andar apurado.
    • Aun así, esperaría que algo así no matara el disco aunque corriera durante años.
  • La solución de “esto es una imagen. Ahora ya no sé cómo embeber un tuit” me gusta mucho más, de hecho.

    • Así el artículo se conserva por más tiempo. Los embeds pueden desaparecer o romperse en cualquier momento.
    • Desde que bloqueé Twitter, muchas veces no veo nada en el lugar del embed, o solo se renderiza un emoji en medio de un espacio vacío, así que estoy de acuerdo.
  • 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 osascript soportan AppleScript y JavaScript casi al mismo nivel, y en JavaScript hay bastantes cosas dentro del objeto $.

    • No tenía idea; voy a probarlo. En cinco minutos recibí dos comentarios muy útiles sobre AppleScript, qué genial.
  • 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í.

    • Ni siquiera se me ocurrió comprobar si existía algo así; sin duda lo voy a revisar. Aunque también hay algo de gracia en que el bucle principal sea AppleScript.
  • 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.

    • Gracias por ambos enlaces. También es interesante que hayas elegido el sistema de archivos, y cuando vuelva a una máquina con Windows en unas semanas quiero revisarlo.
      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...
    • También está Defender of the Favicon: http://www.p01.org/defender_of_the_favicon/
    • La forma en que está implementado el segundo es ingeniosa. Me pregunto si también consideraste implementarlo con FUSE.
    • Para fanáticos de la línea de comandos, también existe rpg-cli.
      https://github.com/facundoolano/rpg-cli
    • No sé qué clase de magia de otro plano es esto.
  • 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”.

    • Sí, encontrar el equilibrio es difícil.
      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.

    • Quitando la emulación de MacOS, me pregunto si solo el Juego de la vida podría hacerse con archivos DS_Store.
      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.