شاهدت مرة فيديو على اليوتيوب لروبوت هاردوير يلعب لعبة piano tiles 2

فأعجبتني الفكرة وقررت أن أبني روبوت مشابهاً له لكن باستخدام السوفتوير بدلاً من الهاردوير.

ومع أن الفكرة فشلت برمجياً, إلا أنني أحببت أن أشارككم التجربة والخطوات التي قمت بها.

الفكرة التي خطرت لي لبرمجة الروبوت كانت أن آخذ لقطات متقطعة للشاشة وفي فترات متقاربة زمنياً, وفي كل frame أبحث في الصورة الملتقطة للشاشة عن المربعات السوداء وأضعط عليها -فكرة ولا أبسط- .

while(true)

    var image=takeScreenshotForDevice();

    for(i=0 to image.width)
        for(j=0 to image.height)
            if(image[i][j] is black)
                clickOnScreenOnPosition(i, j);

بعد البحث قليلاً عن الموضوع وجدت أن الطريقة الوحيدة الممكنة لأخذ صورة لشاشة جهاز الأندرويد كانت تستلزم أن يكون لدى البرنامج صلاحيات root, لان تصوير شاشات التطبيقات الأخرى يعتبر غير آمن وبالتالي غير مسموح به بدون صلاحيات الroot. وكذلك الأمر بالنسبة للضغط على الشاشة, أي أن الروبوت يستلزم أن يكون الجهاز لديه صلاحيات root كي يعمل.

لم تتغير الخوارزمية كثيراً, كل ما كان علي فعله هو تشغيل البرنامج على جهاز بصلاحيات root واستخدام الأمرين التاليين مباشرة من الadb shell

screencap -p /path/to/screenshot.png
/system/bin/input tap $x $y

الأمر الأول يستخدم لالتقاط صورة للشاشة وتخزينها بامتداد png على وحدة الجهاز التخزينية والامر الثاني يستخدم للنقر على الشاشة على الإحداثيات x,y ولم تكن كتابة الكود البرمجي وتشغيله معقدة.


جربت تشغيل الروبوت على المرحلة الأولى في اللعبة twinkle little star, لكن الروبوت لم يكن سريعاً في النقر على المربعات السوداء, كان ينقر على أول واحدة أو اثتنين ولا يستطيع اللحاق بالباقي, كانت خيبة أمل برمجية.

بدأت البحث عن أسباب محتملة لبطئ الروبوت في التعرف على المربعات والنقر عليها بسرعة. أحد الأسباب كانت في استخدام الأمر screencap -p /path/to/screenshot.png حيث كان هذا الأمر لوحده يستغرق حوالي 3-4 ثواني في التنفيذ, وأحد أسباب بطئه أنه يلتقط الصورة بشكل raw (سادس عشري), ثم يقوم بتحويلها إلى صيغة png ومن ثم تخزينها في وحدة الجهاز التخزينية, ثم إعادة تحميلها للبرنامج. فكان لا بد من طريقة أخرى أسرع لالتقاط الصورة.

بحثت في كيفية استخدام الأمر screencap من الterminal

ووجدت أنه بإمكاني تخزين الصورة بشكلها الraw مباشرة, وكل ما علي فعله هو معرفة كيفية تخزين البيانات في الملف بشكلها الخام(raw), وبعد البحث قليلاً استطعت معرفة كيفية تمثيل البيانات الخام(raw) داخل الملف بعد حفظها وتمكنت من تسريع عملية التصوير أكثر.

كما أني حسنت سرعة الخوارزمية الأساسية التي كنت اعمل عليها, بحيث أني ما عدت بحاجة للبحث عن جميع النقاط السوداء في الشاشة, ويكفي أن أفحص 4 نقاط فقط

while(true)

    var image=takeQuickScreenshotForDevice();

    var w = (int){image.width/8}

    var h = (int){Any random value less than imahe.height}


    if(image[w][h] is black)

        clickOnScreenOnPosition(w, h);

    if(image[w*3][h] is black)

        clickOnScreenOnPosition(w*3, h);

    if(image[w*5][h] is black)

        clickOnScreenOnPosition(w*5, h);

    if(image[w*7][h] is black)

        clickOnScreenOnPosition(w*7, h);

كانت تلك محاولتي قبل الأخيرة لحل هذه المشكلة بعد حوالي شهر ونصف من التسلية والبحث والتجريب, أصبح البرنامج أسرع قليلاً لكنه لم يتجاوز للأسف ال4 أو 5 مربعات :3

كانت محاولتي الأخيرة البرمجة مباشرة على الterminal من خلال الadb shell بدل البرمجة باستخدام الجافا, لكن الأمر لم يفلح أيضاً.

بإمكانكم مشاهدة آخر نسخة من الكود(Android + Bash) على الgithub من هنا

الكود غير مرتب جيدا من ناحية oop, لكنه صغير ومليء بالتعليقات والتوضيحات وأرجو أن يكون واضحاً لكم.

بإمكانكم استخدام الكود البرمجي وتعديله كما هو موضح في تفاصيل المشروع على المستودع. وبانتظار آرائكم ونصائحكم حول الكود البرمجي والخطوات التي قمت بها.

إن كان هنلك شيء مهم قد تعلمته وندمت على عدم فعله أثناء العمل في هذا المشروع هو أهمية توثيق التقدم في العمل أولاً باول, ولو أنني وثقت العمل بصور أو فيديو أثناء تنفيذ الكود وتجريبه لكان الشرح عن البرنامج أسهل وأكثر متعة.