1 Punkte von GN⁺ 2023-10-09 | 1 Kommentare | Auf WhatsApp teilen
  • Flappy Dird ist eine experimentelle Umsetzung, die Flappy Bird ausführt, indem sie die Dateiliste des macOS Finder als Bildschirm sowie Dateiauswahl- und Öffnen-Aktionen als Eingabe nutzt
  • Die Kernidee geht davon aus, den nur im Finder aktualisierten Wert Date Last Opened eines Verzeichnisses wie einen Button auszulesen und den Bildschirm durch Umbenennen von Symlink-Dateinamen zu zeichnen
  • Einfaches Rendering per Dateinamenänderung war langsam und verursachte starkes Screen Tearing; verbessert wurde es durch Double Buffering, bei dem per AppleScript das Zielverzeichnis des Finder-Fensters gewechselt wird
  • Eingaben per Doppelklick waren wegen der auf Sekunden begrenzten Zeitstempelauflösung oberhalb von 2 FPS unzuverlässig; später wurde auf das Auslesen der ausgewählten Elemente im Finder umgestellt
  • In der finalen Architektur läuft die Schleife in AppleScript, während Python Spielzustand und Rendering übernimmt; sie arbeitet mit 4 FPS, doch verpasste Eingaben und Finder-typische Einschränkungen bleiben bestehen

Den Finder als Spielbildschirm nutzen

  • Flappy Dird ist ein Finder-basiertes Spiel, dessen Code im GitHub-Repository eingesehen und selbst ausgeführt werden kann
  • Das Spiel enthält eine Anleitung, Highscore-Tracking und scrollende Bannerwerbung
  • Nutzer starten das Spiel per Doppelklick und lassen den Vogel springen, indem sie im Finder-Fenster eine beliebige Datei auswählen
  • Die Ausführungsgeschwindigkeit liegt bei etwa 4 Frames pro Sekunde; deutlich schneller zu laufen ist schwierig, und Eingaben gehen gelegentlich verloren

Date Last Opened wie einen Button verwenden

  • Ausgangspunkt war die Tatsache, dass der Finder für Verzeichnisse ein Feld Date Last Opened besitzt
  • Beobachtet wurden drei Verhaltensweisen
    • Wenn man per cd in ein Verzeichnis wechselt, wird der Zeitstempel nicht aktualisiert
    • Wenn man im Finder einen Symlink auf ein Verzeichnis doppelklickt, wird der Zeitstempel aktualisiert
    • Mit mdls lässt sich dieser Wert mit sekundengenauer Präzision auslesen
  • Diese Eigenschaft ließ sich nutzen, um im Finder einen Button zu bauen
    • Im Verzeichnis dir wird ein Symlink button angelegt, der auf dir zeigt
    • Beim Start wird die letzte Öffnungszeit von dir gelesen
    • Der Zeitstempel wird wiederholt abgefragt; ändert sich der Wert, wird das als Eingabe verarbeitet
    • Ohne die Finder-Position zu ändern, wird button geöffnet, um die letzte Öffnungszeit von dir zu verändern

Einen 15x15-Bildschirm mit Emoji-Dateinamen zeichnen

  • ASCII-Art mit der Standardschrift des Finders passgenau auszurichten war schwierig; genutzt wurde stattdessen, dass Emojis in Dateinamen mit gleichmäßiger Breite dargestellt werden
  • Der Prototyp legte in dir 15 untergeordnete Symlinks an, die wiederum auf dir zeigten, und änderte die Namen dieser Links in Emoji-Zeilen, um den Bildschirm aufzubauen
  • Die Rendering-Funktion nimmt bird_y_pos, pipe_locations und frame entgegen und erzeugt ein 15x15-Emoji-Raster
  • In jedem Frame werden die Namen aller Symlinks im Verzeichnis in die jeweiligen Zeilen des Emoji-Rasters geändert
  • Außerdem war ein Hack nötig, der die Reihenfolge der Umbenennungen anpasste, damit der Finder nach Date Modified sortiert
  • Dieser Ansatz funktionierte, war aber sehr langsam und verursachte starkes Screen Tearing

AppleScript und Double Buffering

  • Auf der Suche nach einer Möglichkeit, die Finder-Dateiliste zu aktualisieren und Screen Tearing zu reduzieren, wurde der AppleScript-Befehl tell application "Finder" to tell front window to update every item verwendet
  • Dieser Befehl half etwas, doch das Tearing blieb; anschließend führte der Weg dazu, den Finder direkt per AppleScript zu steuern
  • Es gab mehrere Kandidaten für Double Buffering
    • Ein Ansatz, den Verzeichnisinhalt atomar über Symlinks auszutauschen, passte nicht, weil der Finder den Inhalt von Symlink-Verzeichnissen nicht aufklappen kann
    • Ein Ansatz mit zwei Verzeichnissen, die gegenseitig aufeinander zeigen, konnte Tearing reduzieren, passte aber nicht zum Spiel, weil Frames nur weiterlaufen konnten, wenn der Nutzer klickte
    • Benötigt wurde eine Methode, die das vom Finder angezeigte Verzeichnis ändert, ohne die „letzte Öffnungszeit“ zu verändern
  • Die Lösung war, das Zielverzeichnis des Finder-Fensters per AppleScript mit tell application "Finder" to set target of front Finder window to ("PATH" POSIX file) zu wechseln
  • Nachdem Double Buffering auf diese Weise umgesetzt war, lief das Spiel bei 1 FPS flüssig, war aber weiterhin langsam

Wechsel der Eingabemethode: vom Doppelklick zur Dateiauswahl

  • Doppelklick-Eingaben stießen oberhalb von 2 FPS an Grenzen, weil die Zeitstempelauflösung nur sekundenweise war
  • Da sich per AppleScript die ausgewählten Elemente im Finder-Fenster auslesen lassen, wurde die Eingabe auf „beliebige Datei auswählen“ umgestellt
  • Die neue Spielschleife hatte folgende Struktur
    • Warten, bis der Nutzer doppelklickt
    • In jedem Frame per AppleScript prüfen, ob im aktuellen Fenster eine Datei ausgewählt ist
    • Wenn eine Auswahl vorhanden ist oder sich die letzte Öffnungszeit geändert hat, springt der Vogel
    • Per AppleScript das vom Finder angezeigte Verzeichnis wechseln
    • Für die verbleibende Zeit warten, um die Ziel-Framerate einzuhalten
  • Dieser Ansatz erreichte ungefähr 2 FPS, aber ein AppleScript-Aufruf dauerte etwa 0,2 Sekunden, was hohe Fixkosten verursachte
  • Selbst bei 2 FPS konnte eine Eingabe verpasst werden, wenn der Nutzer nach der Eingabeprüfung eine Datei auswählte

Die Hauptschleife nach AppleScript verlagern

  • Profiling zeigte, dass ein AppleScript, das die Zahl 1 ins Log schreibt und beendet wird, etwa 0,14 Sekunden benötigte
  • Später wurde korrigiert, dass diese Messung keine faire Messung der AppleScript-Startzeit war; gemessen an einem leeren AppleScript könnten es eher etwa 0,06 Sekunden sein
  • Um die Startkosten von AppleScript pro Frame zu vermeiden, wurde die Hauptschleife nach AppleScript verlagert, während Python Spiellogik und Rendering übernahm
  • Die Grundstruktur sieht wie folgt aus
    • Mit game.py await auf den Doppelklick des Nutzers zum Start warten
    • Mit game.py start-frame die Startzeit des Frames aufzeichnen
    • Die Anzahl der ausgewählten Finder-Elemente auslesen, an game.py tick übergeben und den Frame rendern
    • Den von tick vorbereiteten Verzeichnisnamen entgegennehmen und das Ziel des Finder-Fensters auf dieses Verzeichnis setzen
    • Mit game.py sleep die Ziel-Framerate einhalten und wiederholen, solange der Spieler nicht verloren hat
  • Später konnten start-frame und sleep zusammengeführt werden, und eine Schleife für den Neustart nach dem Tod wurde ergänzt

Detailarbeiten, die zur Fertigstellung des Spiels nötig waren

  • Der Spielzustand wird in einer Datei state.json angelegt und in jedem Frame gelesen und geschrieben
  • Da die verwendeten Textzeichen schmaler als Emojis waren, wurden viele hartcodierte Abstände verwendet, um die Ausrichtung passend zu machen
  • Der scrollende Bannertext am oberen Rand war besonders knifflig; dafür wurde eine Funktion read_n_ad_chars erstellt, die abschätzt, wie viele Zeichen auf einmal angezeigt werden sollen
  • Statt das Arbeitsverzeichnis an AppleScript zu übergeben, werden in einem Python-Aufruf first-time-setup Template-Variablen ersetzt
  • Weil AppleScript die Python-Ausgabe verschluckte, wurden Logs per Append in eine Datei geschrieben und später mit cat geprüft
  • Um das Spielgefühl zu verbessern, springt der Vogel eine Zeile weiter nach oben, wenn in zwei aufeinanderfolgenden Frames getippt wird

Einschränkungen eines Finder-Spiels ohne Engine

  • Der Prototyp umfasste etwa 90 Zeilen, der finale Code etwa 550 Zeilen, von denen ungefähr ein Drittel Boilerplate oder Konstanten sind
  • Ohne Game Engine den Frame-Zustand in einem kleinen 2D-Array zu halten, machte es leicht, das gesamte Spiel im Kopf zu behalten
  • 4 FPS und eingeschränkte Eingaben sind große Beschränkungen, doch es scheint möglich, mit derselben Methode auch andere Spiele zu bauen
  • Auch die Idee, Tetris im Finder zu bauen, kam auf und bleibt als schwieriges, aber möglicherweise machbares Beispiel stehen

1 Kommentare

 
GN⁺ 2023-10-09
Hacker-News-Kommentare
  • Ich war sprachlos, so beeindruckt war ich; und obwohl ich mich selbst für jemanden halte, der outside the box denkt, hätte ich vor dem Lesen dieses Artikels wohl gesagt, dass das unmöglich sei.
    Es ist wirklich schön zu sehen, wie jemand bei einem Projekt, das er als Wochenend-Challenge für sich selbst gebaut hat, austestet, ob es tatsächlich machbar ist; der Text war außerdem sehr unterhaltsam zu lesen.

    • In Software sagt man deshalb nie, etwas sei unmöglich. Man braucht nur Zeit und Kreativität.
  • Bob merkt nicht, dass er Flappy Bird auf MacOS laufen gelassen hat, als er am Freitagnachmittag hastig Jacke und Rucksack schnappt, um mit dem Team auf ein Bier zu gehen.
    Über das lange Wochenende mit Feiertag verbraucht sich die Schreib-Lebensdauer der Laptop-SSD mit 4 Frames pro Sekunde, und als Bob am Dienstag zurückkommt, stellt er fest, dass die SSD nicht mehr funktioniert.

    • Ich frage mich, ob jemand, der in der Firma Flappy Dird laufen lässt, wirklich so in Eile sein kann.
    • Trotzdem würde ich erwarten, dass das Laufwerk selbst nach ein paar Jahren Dauerbetrieb nicht stirbt.
  • Mir gefällt die Variante „Das ist ein Bild. Ich weiß jetzt nicht mehr, wie man Tweets einbettet“ sogar deutlich besser.

    • So bleibt der Artikel länger erhalten. Embeds können jederzeit verschwinden oder kaputtgehen.
    • Seit ich Twitter blockiert habe, sehe ich an Embed-Stellen oft gar nichts mehr oder nur ein Emoji, das mitten in einer leeren Fläche gerendert wird; daher stimme ich zu.
  • Ich frage mich, ob OP das Startgeschwindigkeitsproblem von AppleScript lösen könnte, indem er es in JavaScript neu schreibt.
    Seit einigen Jahren unterstützen das Standard-Framework und das Kommandozeilentool osascript AppleScript und JavaScript nahezu gleichwertig, und in JavaScript steckt im $-Objekt ziemlich viel.

    • Davon wusste ich überhaupt nichts; das muss ich ausprobieren. Schön, innerhalb von fünf Minuten gleich zwei sehr hilfreiche Kommentare zu AppleScript bekommen zu haben.
  • Um AppleScript-Events nativ aus Python aufzurufen, könnte man auch py-appscript ausprobieren.
    https://github.com/hhas/appscript/tree/master/py-appscript
    Ich habe nur die Ruby-Version von Appscript verwendet, aber wenn die Python-Version genauso funktioniert, wirkt sie wie eine ideale Wahl, bei der AppleScript selbst überflüssig werden könnte.

    • Mir wäre nicht einmal eingefallen zu prüfen, ob es so etwas gibt; das sehe ich mir auf jeden Fall an. Allerdings macht es auch ein bisschen Spaß, dass die Kernschleife aus AppleScript besteht.
  • Wenn du solche Spiele an Orten, an denen sie nicht sein sollten, magst, gibt es noch absurdere Projekte.
    Fontemon: ein Spiel in einer Schriftart https://www.coderelay.io/fontemon.html
    Dungeons & Directories: ein Textadventure im Dateibrowser https://wheybags.com/dungeons_and_directories/ — das habe ich gemacht

    • Danke für beide Links. Auch die Wahl des Dateisystems ist interessant; wenn ich in ein paar Wochen wieder an einem Windows-Rechner bin, möchte ich mir das ansehen.
      Mit dem Font-Spiel habe ich auch ein wenig herumgespielt, und ich habe Hexagone[1] gebaut, um zu sehen, wie es sich anfühlt, eine Schriftart zu schreiben. Ich war mir danach zwar nicht sicher, welches Spiel ich bauen sollte, aber es macht Spaß, dadurch solche Inspiration zu bekommen.
      [1] http://eieio.games/nonsense/hexagone-converting-hex-to-rgb-w...
    • Es gibt auch Defender of the Favicon: http://www.p01.org/defender_of_the_favicon/
    • Die zweite Implementierung ist genial. Ich frage mich allerdings, ob auch eine Umsetzung mit FUSE in Betracht gezogen wurde.
    • Für Kommandozeilen-Nerds gibt es auch rpg-cli.
      https://github.com/facundoolano/rpg-cli
    • Ich habe keine Ahnung, was für jenseitige Magie das sein soll.
  • Ich mag die Haltung „Das geht noch besser“, die sich durch den ganzen Artikel zieht, wirklich sehr. Ich frage mich, wie es wäre, wenn alle Entwickler so eine Einstellung hätten.
    Wenn das natürlich alle täten, würde nie etwas fertig, weil es immer noch besser geht; manchmal braucht man jemanden, der sagt: „Das ist gut genug.“

    • Stimmt, die Balance ist schwierig.
      Bei solchen Hobbyprojekten ist es leichter, bei „das geht noch besser“ zu bleiben. Bei der Arbeit überlegt man normalerweise, ob der Nutzen, noch einen Tag zum Feinschliff zu investieren, größer ist als alles andere, was wir tun wollen.
      Aber bei Flappy Dird wollte ich weitermachen, bis ich entweder mit dem Projekt zufrieden war oder glaubte, dass ich das Spiel nicht weiter treiben kann.
  • Vom Titel her dachte ich zuerst, es würde darum gehen, in der Icon-Ansicht eines Finder-Fensters per AppleScript Icons wie Sprites zu bewegen.
    Dass es tatsächlich in der Listenansicht umgesetzt ist und anfangs über das Polling des „zuletzt geöffnet“-Datums eines Verzeichnisses funktionierte, hat mich überrascht; das ist definitiv ein kreativer Ansatz.

  • Ich liebe den Geist dieses Artikels, kleine Spiele an jeden nur möglichen Ort zu bringen.
    Er erinnert mich an das Fortune Teller fish-Taskleisten-Widget, das man früher in GNOME nutzen konnte.
    Ganz unabhängig davon ließ mich die Großschreibung „MacOS Finder“ zuerst an eine Erweiterung für MacOS 8 oder 9 denken, aber das tatsächliche Ergebnis war viel unterhaltsamer.

  • Die nächste Challenge: im Finder Game of Life ausführen und darin einen Mac emulieren, auf dem Finder läuft.

    • Wenn man die MacOS-Emulation weglässt und nur Game of Life macht, frage ich mich, ob das vielleicht mit DS_Store-Dateien möglich wäre.
      Man erstellt zunächst Ordner entsprechend dem Muster und sortiert sie per Rechtsklick, sodass sie im Raster liegen. Danach liest ein Skript die Ordnerpositionen aus der DS_Store-Datei und aktualisiert sie auf die neuen Positionen für den nächsten Frame oder erstellt und löscht bei Bedarf Ordner.