- Flappy Dird, macOS Finder की file list को स्क्रीन और file select/open actions को input की तरह इस्तेमाल करके Flappy Bird चलाने का एक experimental implementation है
- इसका core idea यह है कि सिर्फ Finder में अपडेट होने वाले directory के Date Last Opened value को button की तरह पढ़ा जाए, और symbolic link के filenames बदलकर स्क्रीन draw की जाए
- सिर्फ filename बदलकर render करने का तरीका धीमा था और screen tearing बहुत ज़्यादा थी, इसलिए AppleScript से Finder window की target directory बदलने वाली double buffering technique से इसे बेहतर किया गया
- double-click input, second-level timestamp limitation की वजह से 2 FPS से ऊपर unstable था, इसलिए बाद में Finder के selected items पढ़ने वाले तरीके पर switch किया गया
- final architecture में AppleScript loop चलाता है और Python game state व rendering संभालता है; यह 4 FPS पर चलता है, लेकिन input miss होना और Finder की अपनी limitations अब भी बनी हुई हैं
Finder को game screen की तरह इस्तेमाल करना
- Flappy Dird, GitHub repository में उपलब्ध code के साथ खुद चलाकर देखने लायक Finder-based game है
- game में instructions, high score tracking, और scrolling banner ads शामिल हैं
- user double-click करके game शुरू करता है, और Finder window के अंदर किसी भी file को select करके bird को jump कराता है
- इसकी run speed लगभग 4 frames per second है; इससे बहुत तेज़ चलाना मुश्किल है और input भी कभी-कभी miss हो जाता है
Date Last Opened को button की तरह इस्तेमाल करना
- शुरुआती point यह था कि Finder में directories के लिए Date Last Opened field मौजूद होती है
- देखे गए behavior तीन तरह के थे
cdसे directory में जाने पर timestamp अपडेट नहीं होता- Finder में directory के symbolic link को double-click करने पर timestamp अपडेट होता है
mdlsसे इस value को second-level precision के साथ पढ़ा जा सकता है
- इस property का इस्तेमाल करके Finder के अंदर button बनाया जा सकता था
dirdirectory के अंदरdirको point करने वालाbuttonsymbolic link बनाया गया- start पर
dirका last opened time पढ़ा जाता है - बार-बार timestamp poll किया जाता है, और value बदलते ही उसे input माना जाता है
- Finder location बदले बिना
buttonखोला जाता है ताकिdirका last opened time बदल जाए
emoji filenames से 15x15 स्क्रीन draw करना
- Finder के default font में ASCII art को align करना मुश्किल था, लेकिन filenames में डाले गए emoji एक fixed width में दिखते हैं, इस बात का फायदा उठाया गया
- prototype में
dirके अंदर फिर सेdirको point करने वाले 15 sub symbolic links बनाए गए, और हर link के नाम को emoji row में बदलकर स्क्रीन बनाई गई - rendering function,
bird_y_pos,pipe_locations,framevalues लेकर 15x15 emoji grid बनाता है - हर frame पर directory के सभी symbolic link names को emoji grid की अलग-अलग rows में बदल दिया जाता है
- Finder को
Date Modifiedके आधार पर sort कराने के लिए rename order को adjust करने वाला hack भी ज़रूरी था - यह तरीका काम करता था, लेकिन बहुत धीमा था और screen tearing काफ़ी ज़्यादा थी
AppleScript और double buffering
- screen tearing कम करने के लिए Finder file list को refresh करने का तरीका खोजते हुए AppleScript command
tell application "Finder" to tell front window to update every itemका इस्तेमाल किया गया - इस command से थोड़ी मदद मिली, लेकिन tearing बनी रही, और इसके बाद AppleScript से Finder को सीधे control करने की दिशा में काम बढ़ा
- double buffering के लिए कई candidates थे
- symbolic link से directory contents को atomically replace करने वाला तरीका इसलिए फिट नहीं बैठा क्योंकि Finder symbolic-linked directory contents को expand नहीं कर पाता
- एक-दूसरे को point करने वाली दो directories वाला तरीका tearing कम कर सकता था, लेकिन उसमें frame सिर्फ user click पर advance हो पाता, जो game के लिए ठीक नहीं था
- ऐसा तरीका चाहिए था जिसमें Finder जो directory दिखा रहा है वह बदले, लेकिन “last opened time” न बदले
- समाधान AppleScript के
tell application "Finder" to set target of front Finder window to ("PATH" POSIX file)से Finder window की target directory बदलना था - इस तरीके से double buffering लागू करने पर game 1 FPS पर smooth चलने लगा, लेकिन speed अब भी धीमी थी
input method में बदलाव: double-click से file selection तक
- double-click input में timestamp precision सिर्फ seconds तक होने की वजह से 2 FPS से ऊपर limitation थी
- AppleScript से Finder window के selected items पढ़े जा सकते थे, इसलिए input को “किसी भी file को select करना” बना दिया गया
- नया game loop इस तरह था
- user के double-click करने तक wait करना
- हर frame पर AppleScript से check करना कि current window में कोई file selected है या नहीं
- selection होने पर या last opened time बदलने पर bird को jump कराना
- AppleScript से Finder जो directory दिखा रहा है उसे बदलना
- target frame rate बनाए रखने के लिए बचा हुआ समय wait करना
- इस तरीके से 2 FPS तक कुछ हद तक पहुँचना संभव हुआ, लेकिन AppleScript call में लगभग 0.2 seconds लगते थे, इसलिए fixed cost बड़ी थी
- 2 FPS पर भी, अगर input check के बाद user file select करे तो input miss हो सकता था
main loop को AppleScript में ले जाना
- profiling में यह दिखा कि number
1को log में लिखकर exit करने वाली AppleScript को लगभग 0.14 seconds लगते हैं - बाद में यह correction जोड़ा गया कि यह AppleScript startup time का fair measurement नहीं था, और empty AppleScript के लिए यह लगभग 0.06 seconds के करीब हो सकता है
- हर frame पर AppleScript startup cost देने से बचने के लिए main loop को AppleScript में ले जाया गया, और Python को game logic व rendering का ज़िम्मा दिया गया
- basic structure इस तरह था
game.py awaitसे user के double-click start का इंतज़ार करनाgame.py start-frameसे frame start time record करना- Finder selection item count पढ़कर उसे
game.py tickको देना और frame render करना tickद्वारा तैयार की गई directory name लेकर Finder window का target उस directory पर set करनाgame.py sleepसे target frame rate बनाए रखना, और player हारा न हो तो repeat करना
- बाद में
start-frameऔरsleepको merge किया जा सका, और death के बाद restart के लिए loop जोड़ा गया
game पूरा करने के लिए ज़रूरी detail work
- game state के लिए
state.jsonfile बनाई गई, जिसे हर frame पर read और write किया जाता है - text में इस्तेमाल किए गए characters की width emoji से कम थी, इसलिए alignment के लिए काफ़ी hardcoded spacing का इस्तेमाल करना पड़ा
- ऊपर scrolling banner text खास तौर पर मुश्किल था, इसलिए एक बार में कितने characters दिखें इसका अंदाज़ा लगाने के लिए
read_n_ad_charsfunction बनाया गया - working directory को AppleScript में pass करने के बजाय,
first-time-setupPython call के दौरान template variables replace किए गए - AppleScript Python output को निगल जाता था, इसलिए logs को file में append करके बाद में
catसे देखा गया - लगातार दो frames में tap करने पर bird एक line और ऊपर jump करे, ऐसा करके game feel बेहतर बनाया गया
engine के बिना बनाए गए Finder game की limitations
- prototype लगभग 90 lines का था, और final code लगभग 550 lines का है, जिनमें लगभग एक-तिहाई boilerplate या constants हैं
- game engine के बिना छोटे 2D array में frame state रखने का तरीका पूरे game को दिमाग में संभालकर रखने के लिए आसान था
- 4 FPS और limited input बड़ी limitations हैं, लेकिन इसी तरीके से दूसरे games बनाना भी संभव लगता है
- Finder में Tetris बनाने का idea भी आया, और यह मुश्किल होने के बावजूद संभव हो सकने वाले उदाहरण की तरह बचा हुआ है
1 टिप्पणियां
Hacker News की रायें
इतना हैरान करने वाला कि बोलती बंद हो गई; मैं खुद को out-of-the-box सोचने वाला मानता था, फिर भी यह लेख पढ़ने से पहले शायद कहता कि यह नामुमकिन है
वीकेंड पर खुद को चुनौती देने के लिए बनाए गए प्रोजेक्ट को सच में संभव है या नहीं, यह देखने के लिए इतनी दूर तक धकेलते देखना बहुत अच्छा लगा, और लेख भी बेहद मज़ेदार था
Bob शुक्रवार दोपहर बीयर पीने जा रही टीम के साथ निकलने की जल्दी में अपनी जैकेट और बैकपैक उठाता है, और यह नोटिस नहीं करता कि उसने MacOS पर चलने वाला Flappy Bird खुला छोड़ दिया है
बैंक हॉलिडे वाले वीकेंड के दौरान laptop SSD की write life 4 frames per second की रफ्तार से खर्च होती रहती है, और मंगलवार को लौटकर Bob देखता है कि SSD अब काम नहीं कर रहा
“यह image है। अब ट्वीट embed कैसे करते हैं, पता नहीं” वाला तरीका मुझे उल्टा कहीं ज़्यादा पसंद है
सोचता हूँ कि क्या OP AppleScript startup speed वाली समस्या को JavaScript में दोबारा लिखकर हल कर सकता है
कुछ सालों से default framework और command-line tool
osascriptAppleScript और JavaScript को लगभग बराबरी से support करते हैं, और JavaScript में$object के अंदर काफी कुछ मिलता हैPython से AppleScript events को native तौर पर call करना हो तो py-appscript भी आज़मा सकते हैं
https://github.com/hhas/appscript/tree/master/py-appscript
मैंने सिर्फ Ruby version Appscript इस्तेमाल किया है, लेकिन अगर Python version भी उसी तरह काम करता है तो यह इतना ideal विकल्प लगता है कि AppleScript खुद की ज़रूरत ही न रहे
अगर आपको ऐसे उन जगहों पर games जहाँ वे नहीं होने चाहिए पसंद हैं, तो और भी ज़्यादा बेतुके projects हैं
Fontemon: font के अंदर game https://www.coderelay.io/fontemon.html
Dungeons & Directories: file browser के अंदर text adventure https://wheybags.com/dungeons_and_directories/ — यह मैंने बनाया है
font game भी थोड़ा छेड़ा था, और font लिखना कैसा लगता है यह देखने के लिए Hexagone[1] बनाया था। कौन सा game बनाऊँ, इसका भरोसा तो नहीं मिला, लेकिन ऐसी inspiration आना मज़ेदार है
[1] http://eieio.games/nonsense/hexagone-converting-hex-to-rgb-w...
https://github.com/facundoolano/rpg-cli
पूरे लेख में बहती “इसे और बेहतर किया जा सकता है” वाली attitude सच में अच्छी लगी। सोचता हूँ अगर हर developer की यही mindset हो तो कैसा हो
बेशक, अगर सभी ऐसे हों तो हमेशा और बेहतर किया जा सकता है, इसलिए कुछ भी कभी खत्म नहीं होगा, और कभी-कभी “इतना काफी है” कहने वाला व्यक्ति चाहिए होता है
ऐसे hobby projects में “इसे और बेहतर किया जा सकता है” वाली दिशा बनाए रखना आसान होता है। काम में आम तौर पर सोचना पड़ता है कि एक और दिन polish करने की value उन बाकी सभी चीज़ों से बड़ी है या नहीं जो हम करना चाहते हैं
लेकिन Flappy Dird में मैं तब तक चलते रहना चाहता था जब तक project से संतुष्ट न हो जाऊँ, या यह भरोसा न हो जाए कि game को अब और आगे नहीं धकेला जा सकता
title देखकर शुरू में लगा था कि Finder window के icon view में AppleScript से icons को sprites की तरह move कराने वाला तरीका होगा
असल में यह list view में implement किया गया था, और शुरुआत में directory की “last opened date” को poll करने का तरीका देखकर हैरानी हुई; यह निश्चित ही creative approach है
छोटे game को हर संभव जगह ले जाने वाली इस लेख की spirit सच में मज़ेदार है
पुराने GNOME में इस्तेमाल हो सकने वाला Fortune Teller fish taskbar widget याद आ गया
बिल्कुल अलग बात, “MacOS Finder” वाली capitalization देखकर पहले लगा था कि यह MacOS 8 या 9 के लिए कोई extension होगा, लेकिन असली नतीजा कहीं ज़्यादा मज़ेदार निकला
अगली चुनौती है Finder के अंदर Game of Life चलाना, और उसके अंदर Finder चलाने वाले Mac को emulate करना
पहले folders को pattern के अनुसार बनाकर right-click करके sort करें तो वे grid में रखे जाएँगे। फिर script DS_Store file से folder positions पढ़े, और उन्हें अगले frame की नई positions में update करे, या ज़रूरत हो तो folders बनाए और delete करे