1 points par GN⁺ 2023-10-09 | 1 commentaires | Partager sur WhatsApp
  • Flappy Dird est une implémentation expérimentale de Flappy Bird qui s’exécute en utilisant la liste de fichiers du Finder de macOS comme écran, et les actions de sélection et d’ouverture de fichiers comme entrées
  • L’idée centrale consiste à lire la valeur de Date Last Opened d’un répertoire, mise à jour uniquement dans le Finder, comme s’il s’agissait d’un bouton, et à dessiner l’écran en changeant les noms de fichiers de liens symboliques
  • Le rendu par simple renommage de fichiers était lent et provoquait beaucoup de déchirures d’écran, ce qui a conduit à une amélioration via une forme de double buffering consistant à changer, avec AppleScript, le répertoire cible de la fenêtre du Finder
  • L’entrée par double-clic était instable au-delà de 2 FPS à cause de la limite des horodatages à la seconde, puis elle a été remplacée par une méthode lisant les éléments sélectionnés du Finder
  • L’architecture finale repose sur une boucle en AppleScript tandis que Python gère l’état du jeu et le rendu ; le tout fonctionne à 4 FPS, mais avec encore des entrées parfois perdues et les contraintes propres au Finder

Utiliser le Finder comme écran de jeu

  • Flappy Dird est un jeu basé sur le Finder dont on peut consulter le code et qu’on peut exécuter soi-même depuis le dépôt GitHub
  • Le jeu inclut un mode d’emploi, le suivi du meilleur score et une bannière publicitaire défilante
  • L’utilisateur démarre la partie par double-clic, puis fait sauter l’oiseau en sélectionnant n’importe quel fichier dans la fenêtre du Finder
  • La vitesse d’exécution tourne autour de 4 images par seconde, et il est difficile d’aller beaucoup plus vite ; de plus, certaines entrées sont parfois perdues

Utiliser Date Last Opened comme un bouton

  • Le point de départ était l’existence, dans le Finder, d’un champ Date Last Opened pour les répertoires
  • Trois comportements ont été observés
    • même en entrant dans un répertoire avec cd, l’horodatage n’est pas mis à jour
    • si l’on double-clique dans le Finder sur un lien symbolique vers un répertoire, l’horodatage est mis à jour
    • la valeur peut être lue avec une précision à la seconde via mdls
  • Cette propriété a permis de créer un bouton dans le Finder
    • création, dans le répertoire dir, d’un lien symbolique button pointant vers dir
    • lecture de la date de dernière ouverture de dir au démarrage
    • interrogation répétée de l’horodatage et traitement comme entrée lorsqu’il change
    • ouverture de button sans changer l’emplacement affiché dans le Finder, afin de modifier la date de dernière ouverture de dir

Dessiner un écran 15x15 avec des noms de fichiers en emoji

  • Il était difficile d’aligner un ASCII art avec la police par défaut du Finder, mais les emoji insérés dans les noms de fichiers s’affichaient avec une largeur régulière, ce qui a été exploité
  • Le prototype créait, dans dir, 15 sous-liens symboliques pointant eux aussi vers dir, puis composait l’écran en changeant le nom de chaque lien pour en faire une ligne d’emoji
  • La fonction de rendu prenait bird_y_pos, pipe_locations et frame et produisait une grille 15x15 en emoji
  • À chaque frame, le nom de tous les liens symboliques du répertoire était remplacé par les lignes correspondantes de la grille d’emoji
  • Il a aussi fallu un hack pour ajuster l’ordre des renommages afin que le Finder trie selon Date Modified
  • Cette approche fonctionnait, mais elle était très lente et provoquait de fortes déchirures d’écran

AppleScript et double buffering

  • Pour réduire les déchirures, la recherche d’un moyen de forcer le rafraîchissement de la liste de fichiers du Finder a mené à la commande AppleScript tell application "Finder" to tell front window to update every item
  • Cette commande aidait un peu, mais les déchirures persistaient, ce qui a ensuite conduit à piloter directement le Finder avec AppleScript
  • Plusieurs options de double buffering ont été envisagées
    • remplacer atomiquement le contenu d’un répertoire via des liens symboliques ne convenait pas, car le Finder ne peut pas déplier le contenu d’un répertoire symbolique
    • utiliser deux répertoires se pointant mutuellement réduisait les déchirures, mais n’autorisait l’avancement d’une frame qu’au clic de l’utilisateur, ce qui ne convenait pas au jeu
    • il fallait une méthode permettant de changer le répertoire affiché par le Finder sans modifier la “date de dernière ouverture”
  • La solution a été d’utiliser AppleScript pour changer le répertoire cible de la fenêtre du Finder avec tell application "Finder" to set target of front Finder window to ("PATH" POSIX file)
  • Avec cette méthode de double buffering, le jeu tournait de manière fluide à 1 FPS, mais restait malgré tout lent

Changement de méthode d’entrée : du double-clic à la sélection de fichiers

  • L’entrée par double-clic atteignait ses limites au-delà de 2 FPS à cause de la précision à la seconde des horodatages
  • Comme AppleScript pouvait lire les éléments sélectionnés de la fenêtre du Finder, l’entrée a été changée en “sélectionner n’importe quel fichier”
  • La nouvelle boucle de jeu avait la structure suivante
    • attendre que l’utilisateur double-clique pour démarrer
    • à chaque frame, vérifier via AppleScript si un fichier est sélectionné dans la fenêtre courante
    • faire sauter l’oiseau si une sélection existe ou si la date de dernière ouverture a changé
    • changer via AppleScript le répertoire affiché par le Finder
    • attendre le temps restant pour respecter le framerate cible
  • Cette approche permettait d’atteindre à peu près 2 FPS, mais chaque appel AppleScript prenait environ 0,2 seconde, un coût fixe important
  • Même à 2 FPS, une sélection effectuée juste après la vérification de l’entrée pouvait être manquée

Déplacer la boucle principale dans AppleScript

  • Le profilage a montré qu’un AppleScript qui se contentait d’écrire le chiffre 1 dans le log puis de se terminer prenait environ 0,14 seconde
  • Une correction a ensuite été ajoutée : cette mesure n’était pas un benchmark équitable du temps de démarrage d’AppleScript, et un script vide se situerait plutôt autour de 0,06 seconde
  • Pour éviter le coût de démarrage d’AppleScript à chaque frame, la boucle principale a été déplacée dans AppleScript, tandis que Python prenait en charge la logique du jeu et le rendu
  • La structure de base devenait alors la suivante
    • game.py await attend le double-clic de départ de l’utilisateur
    • game.py start-frame enregistre l’heure de début de la frame
    • lecture du nombre d’éléments sélectionnés dans le Finder, transmis à game.py tick pour rendre la frame
    • récupération du nom du répertoire préparé par tick, puis changement de la cible de la fenêtre du Finder vers ce répertoire
    • game.py sleep ajuste le temps d’attente pour respecter le framerate cible, puis la boucle recommence tant que le joueur n’a pas perdu
  • Par la suite, start-frame et sleep ont pu être fusionnés, et une boucle de redémarrage après la mort a été ajoutée

Détails nécessaires pour terminer le jeu

  • L’état du jeu est enregistré dans un fichier state.json, lu et écrit à chaque frame
  • Le texte utilisait des caractères plus étroits que les emoji, ce qui a nécessité beaucoup d’espacements codés en dur pour conserver l’alignement
  • Le texte défilant de la bannière supérieure était particulièrement difficile, ce qui a conduit à la création d’une fonction read_n_ad_chars pour estimer combien de caractères afficher à la fois
  • Au lieu de passer le répertoire de travail à AppleScript, des variables de template ont été remplacées lors d’un appel Python first-time-setup
  • Comme AppleScript avalait la sortie de Python, les logs étaient ajoutés à un fichier puis consultés plus tard avec cat
  • Pour améliorer les sensations de jeu, un appui sur deux frames consécutives permet à l’oiseau de remonter d’une ligne supplémentaire

Les limites d’un jeu Finder sans moteur

  • Le prototype comptait environ 90 lignes, et le code final environ 550 lignes, dont à peu près un tiers de boilerplate ou de constantes
  • Le fait de se passer de moteur de jeu et de stocker l’état de chaque frame dans un petit tableau 2D rendait l’ensemble facile à garder en tête
  • Les 4 FPS et les entrées limitées constituent de fortes contraintes, mais il semble possible de créer d’autres jeux avec la même approche
  • L’idée de réaliser un Tetris dans le Finder a aussi été évoquée, comme un exemple difficile mais peut-être réalisable

1 commentaires

 
GN⁺ 2023-10-09
Avis de Hacker News
  • C’était à couper le souffle, et même si je me considérais comme quelqu’un qui pense hors du cadre, avant de lire cet article j’aurais probablement dit que c’était impossible.
    C’est vraiment génial de voir quelqu’un pousser jusqu’au bout un projet créé comme défi personnel du week-end pour voir s’il est réellement possible, et l’article était aussi très amusant à lire.

    • C’est pour ça qu’en logiciel, je ne dis jamais que quelque chose est impossible. Il suffit de temps et de créativité.
  • Bob se dépêche de prendre sa veste et son sac à dos pour rejoindre l’équipe qui part boire des bières le vendredi après-midi, sans remarquer qu’il a laissé Flappy Bird tournant sous macOS.
    Pendant le long week-end férié, la durée de vie en écriture du SSD de son portable s’épuise à 4 images par seconde, et quand Bob revient le mardi, il découvre que le SSD ne fonctionne plus.

    • Je me demande si quelqu’un du genre à lancer Flappy Dird au bureau serait vraiment susceptible d’être aussi pressé.
    • J’espère quand même qu’à ce rythme, le disque pourrait tourner pendant des années sans mourir.
  • J’aime finalement beaucoup plus l’approche « ceci est une image. Je ne sais plus comment intégrer un tweet maintenant ».

    • Comme ça, l’article se conserve plus longtemps. Les embeds peuvent disparaître ou se casser à tout moment.
    • D’accord. Depuis que j’ai bloqué Twitter, je vois souvent soit rien du tout à l’emplacement des embeds, soit juste un emoji rendu au milieu d’un espace vide.
  • Je me demande si l’OP pourrait résoudre le problème de vitesse de démarrage d’AppleScript en le réécrivant en JavaScript.
    Depuis quelques années, le framework de base et l’outil en ligne de commande osascript prennent en charge AppleScript et JavaScript presque à égalité, et en JavaScript il y a pas mal de choses accessibles via l’objet $.

    • Je ne le savais absolument pas, il faut que j’essaie. En cinq minutes, j’ai reçu deux commentaires très utiles sur AppleScript, c’est super.
  • Pour appeler nativement des événements AppleScript depuis Python, il est aussi possible d’essayer py-appscript.
    https://github.com/hhas/appscript/tree/master/py-appscript
    Je n’ai utilisé que la version Ruby d’Appscript, mais si la version Python fonctionne de la même manière, cela semble être un choix idéal, au point de rendre AppleScript lui-même inutile.

    • Je n’avais même pas pensé à vérifier si ce genre de chose existait, et je vais clairement regarder. Cela dit, il y a aussi un certain plaisir à ce que la boucle principale soit en AppleScript.
  • Si vous aimez ce genre de jeux là où ils ne devraient pas être, il existe des projets encore plus absurdes.
    Fontemon : un jeu dans une police https://www.coderelay.io/fontemon.html
    Dungeons & Directories : une aventure textuelle dans un explorateur de fichiers https://wheybags.com/dungeons_and_directories/ — c’est moi qui l’ai fait.

    • Merci pour les deux liens. Le choix du système de fichiers est aussi amusant, et j’ai envie de regarder ça quand je reviendrai sur une machine Windows dans quelques semaines.
      J’ai aussi un peu bidouillé avec le jeu dans la police, et j’ai créé Hexagone[1] pour voir ce que ça fait d’écrire une police. Je ne suis pas reparti avec une idée claire du jeu à créer, mais c’est amusant que ce genre de chose donne de l’inspiration.
      [1] http://eieio.games/nonsense/hexagone-converting-hex-to-rgb-w...
    • Il y a aussi Defender of the Favicon : http://www.p01.org/defender_of_the_favicon/
    • La façon dont le deuxième est implémenté est brillante. Je me demande cependant si vous avez envisagé de l’implémenter avec FUSE.
    • Pour les mordus de ligne de commande, il y a aussi rpg-cli.
      https://github.com/facundoolano/rpg-cli
    • Je ne sais pas quel genre de magie venue d’un autre monde c’est censé être.
  • J’adore l’attitude « on peut faire mieux » qui traverse tout l’article. Je me demande ce que ça donnerait si tous les développeurs avaient cet état d’esprit.
    Évidemment, si tout le monde faisait ça, comme on peut toujours faire mieux, rien ne serait jamais terminé, et il faut parfois quelqu’un pour dire « c’est assez bien comme ça ».

    • Oui, c’est un équilibre difficile à trouver.
      Sur ce genre de projet perso, c’est plus facile de rester du côté « on peut faire mieux ». Au travail, on finit généralement par se demander si passer une journée de plus à peaufiner vaut plus que toutes les autres choses qu’on veut faire.
      Mais avec Flappy Dird, j’avais envie de continuer jusqu’à être satisfait du projet, ou jusqu’à croire qu’il n’était plus possible de pousser le jeu plus loin.
  • En voyant le titre, j’ai d’abord pensé que ce serait une méthode consistant à déplacer des icônes comme des sprites avec AppleScript dans la présentation par icônes d’une fenêtre Finder.
    En réalité, c’est implémenté en vue liste, et au début cela fonctionne en interrogeant la « date de dernière ouverture » du répertoire ; ça m’a surpris, et c’est clairement une approche créative.

  • J’aime beaucoup l’esprit de cet article, qui consiste à amener de petits jeux absolument partout où c’est possible.
    Cela me rappelle le widget de barre des tâches Fortune Teller fish qui existait dans l’ancien GNOME.
    Sans rapport, en voyant la capitalisation « MacOS Finder », j’ai d’abord cru qu’il s’agissait d’une extension pour MacOS 8 ou 9, mais le résultat réel était bien plus amusant.

  • Le prochain défi : exécuter le jeu de la vie dans Finder, puis y émuler un Mac qui exécute Finder.

    • Sans l’émulation de macOS, juste le jeu de la vie, je me demande si ce serait possible avec les fichiers DS_Store.
      Au départ, on crée les dossiers selon le motif, puis on fait un clic droit pour les trier afin qu’ils se placent sur une grille. Ensuite, un script lit la position des dossiers dans le fichier DS_Store, met à jour leurs nouvelles positions pour l’image suivante, ou crée et supprime des dossiers si nécessaire.