برنامه‌نویسی ربات با پایتون: قدم‌به‌قدم کد ربات جاروبرقی

هر کاری که یک ربات انجام می‌دهد در یک جمله خلاصه می‌شود: سنسورها را بخوان، تصمیم بگیر، چرخ‌ها را تنظیم کن — و دوباره از اول. این آموزش شما را از یک فایل خالی تا یک الگوریتم رقابتی برای ربات جاروبرقی لیگ خانه هوشمند می‌برد، همراه با کد کامل پایتون که می‌توانید در برنامه‌ی لیگ اجرا کنید.

دریافت ابزارها صفحه‌ی مسابقات تمرین در مرورگر
محیط مسابقه‌ی لیگ خانه هوشمند در برنامه‌ی لیگ — نمای بالای یک خانه‌ی مبله با ربات جاروبرقی روی کاشی‌های کف

هیچ پیش‌زمینه‌ای در رباتیک لازم نیست. اگر بتوانید در پایتون یک دستور if و یک حلقه‌ی while بنویسید، همین الان به اندازه‌ی کافی بلدید که این صفحه را تا آخر جلو ببرید. بقیه‌ی چیزها — سنسور، جهت‌یابی، استراتژی پوشش — سر جای خودش توضیح داده می‌شود. اگر هنوز خود مسابقه را نمی‌شناسید، اول لیگ ربات جاروبرقی چیست را بخوانید و بعد به این صفحه برگردید.

قبل از خط اول: آماده‌سازی

سه چیز لازم دارید و همه‌شان در بخش شروع سریع صفحه‌ی اصلی جمع شده‌اند:

داخل ZIP، چیزهایی که به شما مربوط می‌شود این‌ها هستند:

مسیرچیست
دکمه‌ی کد پایهبرنامه‌ی شروعِ رده‌ای که انتخاب کرده‌اید — نقطه‌ی شروع شما، خط به خط توضیح داده شده
examples/robot_code_u14.pyنمونه‌کد رده‌ی U14 — نقطه‌ی شروع شما
examples/robot_code_fs.pyنمونه‌کد رده‌ی First Step
examples/robot_code_u19.pyنمونه‌کد رده‌ی U19 — شامل GPS و باتری
controllers/ و protos/ و plugins/موتور پشت صحنه‌ی دنیا. معمولاً کاری با این‌ها ندارید

برای اینکه ربات راه بیفتد: برنامه را باز کنید ← رده‌ی خود را انتخاب کنید ← فایل رده‌ی خود را انتخاب کنید ← حالت مسابقه ← دکمه‌ی کد پایه را بزنید ← یکی از کدهای پوشه‌ی examples/ را به ربات بدهید ← دکمه‌ی را فشار دهید. ربات شروع به حرکت می‌کند. از این لحظه می‌توانید رفتارش را عوض کنید.

مدل ذهنی: کد شما یک حلقه است

این تنها ایده‌ای است که با فهمیدنش همه‌ی کدهای ربات جا می‌افتند، و دقیقاً همان چیزی است که مبتدی‌ها بیشترین مقاومت را در برابرش دارند. برنامه‌ی شما یک داستان نیست که یک بار از بالا تا پایین اجرا شود. یک قطعه‌کد کوتاه است که شبیه‌ساز آن را بارها و بارها، ده‌ها بار در ثانیه، اجرا می‌کند. هر دور از این حلقه یک «لحظه» برای ربات است: نگاه می‌کند، یک آن فکر می‌کند و چرخ‌ها را کمی می‌چرخاند.

یک لحظه، باز شده، این شکلی است:

  1. بخوان — سنسورهای فاصله چه می‌گویند؟ رو به کدام طرف هستم؟
  2. تصمیم بگیر — چیزی سر راهم هست؟ مستقیم بروم، منحرف شوم یا بچرخم؟
  3. عمل کن — یک سرعت به چرخ چپ بده و یک سرعت به چرخ راست.

کل کار همین است. هر الگوریتم تمیزکاری هوشمندی که در این صفحه می‌بینید، فقط یک پاسخ متفاوت به مرحله‌ی دوم است. این کوچک‌ترین کنترلری است که اجرا می‌شود — اسکلتی که همه‌ی مثال‌های بعدی روی آن ساخته می‌شوند:

حلقه‌ی کنترل خالی

# This whole file IS one moment for the robot.
# The app runs it again ten times every second.

wheelleft = 0 # speed of the left wheel (-25 .. 25)
wheelright = 0 # speed of the right wheel (-25 .. 25)

برنامه‌ی لیگ ضربان قلب کار است: فایل شما را ده بار در ثانیه اجرا می‌کند. یعنی «دنیا را ۳۲ میلی‌ثانیه جلو ببر و بعد کنترل را به من برگردان». وقتی شبیه‌سازی تمام شود مقدار -1 برمی‌گرداند و به همین دلیل شرط حلقه این‌طور نوشته شده است. زمان شبیه‌سازی فقط وقتی جلو می‌رود که شما این تابع را صدا بزنید.

مهم‌ترین نتیجه‌ی این ماجرا

ربات شما نمی‌تواند منتظر بماند. نه time.sleep(1) وجود دارد و نه دستوری به معنای «یک ثانیه به چپ بچرخ». اگر بخوابید، دنیا هم با شما می‌خوابد. اگر حلقه‌ی while تودرتویی بنویسید که تمام نمی‌شود، ربات قفل می‌کند و چرخ‌ها با همان سرعت قبلی می‌مانند.

راه‌حل یک شمارنده است که بین دورها باقی می‌ماند. برای اینکه ۰٫۶۴ ثانیه بچرخید، turning = 20 می‌گذارید (چون ۲۰ ضربدر ۳۲ میلی‌ثانیه تقریباً ۰٫۶۴ ثانیه می‌شود) و بعد هر دور یکی از آن کم می‌کنید تا به صفر برسد. این الگو تقریباً در همه‌ی مثال‌های پایین تکرار می‌شود — نسخه‌ی رباتیکیِ «یادم باشد داشتم چه کار می‌کردم».

چرخاندن چرخ‌ها

ربات دو چرخ محرک دارد، یکی چپ و یکی راست. شما هیچ‌وقت نمی‌گویید «برو جلو»؛ می‌گویید «چرخ چپ با این سرعت، چرخ راست با آن سرعت»، و همین اختلاف بین دو چرخ است که فرمان می‌دهد:

چرخ چپچرخ راستنتیجه
حداکثر، جلوحداکثر، جلوحرکت مستقیم رو به جلو با بیشترین سرعت
حداکثر، عقبحداکثر، عقبحرکت مستقیم به عقب
حداکثر، جلوحداکثر، عقبچرخش در جا، ساعتگرد
حداکثر، عقبحداکثر، جلوچرخش در جا، پادساعتگرد
حداکثر، جلونصف، جلویک قوس ملایم به سمت راست

گرفتن موتورها و حرکت به جلو

# Drive straight ahead, for ever.
MAX_SPEED = 25 # the fastest a wheel can turn

wheelleft = MAX_SPEED
wheelright = MAX_SPEED

تنظیم دو عدد چرخ ساده‌ترین کار ممکن است، و روش استاندارد برنامه برای گفتن این جمله به موتور است: «کاری به رسیدن به یک زاویه‌ی مشخص نداشته باش، فقط با سرعتی که می‌دهم بچرخ». فراموش کردن همین یک خط، شماره‌یک دلیلی است که ربات یک مبتدی تکان نمی‌خورد. نام دقیق دستگاه‌ها و بیشینه‌ی سرعت را از نمونه‌کد داخل پوشه‌ی examples/ بردارید — آن فایل برای همین رباتی نوشته شده که شما دارید می‌رانید.

خواندن ۸ سنسور فاصله و قطب‌نمای IMU

در رده‌های First Step و U14 ربات هشت سنسور فاصله دارد که دور تا دور بدنه‌اش چیده شده‌اند، به‌اضافه‌ی یک واحد اینرسی (IMU) که نقش قطب‌نما را بازی می‌کند. رده‌ی U19 علاوه بر این‌ها یک GPS و یک باتری هم دارد. کل دید ربات از دنیا همین است — نه دوربینی هست، نه نقشه‌ای، و نه فهرستی از اینکه مبلمان کجاست. هر چیزی که الگوریتم شما بداند، باید از همین چند عدد بیرون بکشد.

هشت سنسور را مثل یک حلقه تصور کنید که هرکدام به بیرون خیره شده‌اند:

حلقه‌ی سنسورها از نمای بالا

 ds0 ds7 <- front
 ds1 ds6
 ^
 ds2 (robot) ds5
 ds3 ds4 <- back

اما این نقشه را همین‌طوری باور نکنید. خودتان تأییدش کنید، چون ترتیب شماره‌ها دقیقاً از آن جزئیاتی است که بی‌سروصدا یک بعدازظهر را نابود می‌کند: ربات را آرام به سمت دیوار ببرید و ببینید کدام عددها تکان می‌خورند. دو چیز دیگر هم باید قبل از نوشتن هر منطقی کشف کنید:

IMU به سؤال دیگری جواب می‌دهد: رو به کدام طرف هستم؟ زاویه‌ی yaw آن بر حسب رادیان بین منفی پی و مثبت پی برمی‌گردد. هر وقت خواستید آن را بخوانید به درجه تبدیل کنید، ولی داخل محاسبات همان رادیان را نگه دارید.

ابزار اندازه‌گیری: همه‌چیز را فعال کن و چاپ کن

# Every sensor is simply a variable. Nothing to enable, nothing to read out.
#
# front distance straight ahead, in centimetres (smaller = closer)
# frontleft the same, ahead and to the left
# frontright the same, ahead and to the right
# bumperfront ۱ وقتی نیمه‌ی جلوی حلقه‌ی سپر برخورد کرده
# bumperback  ۱ وقتی نیمه‌ی عقب برخورد کرده
# bumper      ۱ وقتی هرکدام از آن دو برخورد کرده باشد
# color the colour of the floor just ahead
# heading which way the robot faces, 0..359 degrees (U14 and U19)

if front < 40:
 turnleft(1)
else:
 forward(1)

 if step % 10 == 0: # ~3 lines a second, not 30
 print(values, "heading", heading)

این کد را اجرا کنید و در همان حال ربات را با ماوس در صحنه بکشید و ببرید. در دو دقیقه چیدمان سنسورها، عددهای نزدیک و دور، و اینکه هر جهت قطب‌نما به کدام دیوار می‌خورد را می‌فهمید. هر ساعتی که این‌جا بگذارید، سه ساعت رفع اشکال گیج‌کننده را بعداً از شما می‌گیرد.

دو تله‌ی سنسورها

حدس زدن آستانه. «نزدیک» یک حس نیست، یک عدد است. پنل سنسورها را باز کنید و ببینید در همان لحظه‌ای که می‌خواهید ربات بپیچد، سنسور دقیقاً چه عددی می‌گوید. از همان عدد استفاده کنید. یک دوره‌ی نمونه‌برداری فعالش کنید. خواندن یک سنسور خواب یک عدد ثابت می‌دهد که هیچ‌وقت عوض نمی‌شود و شما یک ساعت دستورهای if بی‌گناه خودتان را متهم می‌کنید.

فراموش کردن واحدها. سنسورهای فاصله سانتی‌متر می‌گویند و عدد کوچک‌تر یعنی نزدیک‌تر — برعکس چیزی که بیشتر تازه‌کارها بار اول پر می‌شود. هیچ‌وقت getValue() را بالای حلقه صدا نزنید.

اولین کنترلر واقعی: حرکت و چرخش کنار دیوار

این یک کنترلر کامل و کارکننده است. مستقیم جلو می‌رود و هر وقت سنسورهای جلو دیواری دیدند کمی عقب می‌کشد و به اندازه‌ی تعداد مشخصی دور می‌چرخد و بعد راهش را ادامه می‌دهد. همه‌ی اجزایش را قبلاً دیده‌اید: حلقه، شمارنده‌ای که بین دورها باقی می‌ماند، و زنجیره‌ای از elif که اولین شرط درست برنده است.

کنترلر شماره ۱ — مستقیم برو تا چیزی سد راه شود

# Controller #1 - drive, and turn away from whatever blocks the way.
# Remember: this file runs ten times a second, so 10 = one second.

NEAR = 40 # centimetres. "A wall is close." Find YOUR number
TURN_STEPS = 7 # how many passes the spin lasts (7 = 0.7 s)

if timer > 0: # 1) still finishing the previous turn
 timer -= 1
 wheelleft, wheelright = 25, -25

elif front < NEAR: # 2) wall dead ahead -> spin away
 timer = TURN_STEPS
 wheelleft, wheelright = -10, -10

elif frontleft < NEAR: # 3) something on the left -> veer right
 wheelleft, wheelright = 25, 8

elif frontright < NEAR: # 4) something on the right -> veer left
 wheelleft, wheelright = 8, 25

else: # 5) clear road -> full speed
 wheelleft, wheelright = 25, 25

سه نکته ارزش مکث دارند، چون در هر کنترلری که در عمرتان بنویسید تکرار می‌شوند:

دکمه‌ی شروع را بزنید و تماشا کنید. حالا رباتی دارید که تمیز می‌کند — بد، ولی واقعی. از این‌جا به بعد همه‌چیز درباره‌ی این است که در همان زمان، کف بیشتری تمیز شود.

سه راه برای اینکه واقعاً تمیز کند

کنترلر شماره ۱ پرسه می‌زند. در یک اتاق کوچک بالاخره بیشتر جاها را لمس می‌کند، ولی مدام از روی مسیر خودش رد می‌شود و گوشه‌های کاملی را دست‌نخورده جا می‌گذارد. ربات‌های جاروبرقی واقعی چند رفتار ساده را با هم ترکیب می‌کنند و ربات شما هم باید همین کار را بکند.

۱. برخورد تصادفی — یک خط بهتر از هیچ

ارزان‌ترین ارتقا: وقتی به چیزی خوردید، به اندازه‌ی تصادفی و در جهت تصادفی بچرخید. زاویه‌ی چرخش ثابت می‌تواند ربات را در چرخه‌ای گرفتار کند که تا آخر مسابقه همان مثلث را دوباره و دوباره طی کند؛ تصادفی‌بودن این تقارن را می‌شکند. این هنوز یک استراتژی نیست، ولی به‌عنوان رفتار پشتیبان برای وقتی که رفتار باهوش‌تر گیج می‌شود، عالی است.

وصله‌ای برای کنترلر شماره ۱ — چرخش فرار تصادفی

import random

turning = 0
direction = 1 # +1 = spin one way, -1 = the other

# ... inside the loop, replace the two turn branches with:

 if turning > 0:
 turning -= 1
 ls, rs = MAX_SPEED * direction, -MAX_SPEED * direction

 elif front > NEAR:
 turning = random.randint(10, 35) # 0.32 s .. 1.12 s of spinning
 direction = random.choice((-1, 1)) # a coin flip
 ls, rs = -MAX_SPEED * 0.4, -MAX_SPEED * 0.4

۲. دیوارگرد — اولین رفتاری که واقعاً زمین را پوشش می‌دهد

دیوارگردی یعنی نگه داشتن دیوار در یک فاصله‌ی ثابت از یک طرف ربات و حرکت در امتداد آن. یک بار دور اتاق بگردید و کل محیط اتاق، از جمله گوشه‌هایی که برخورد تصادفی هیچ‌وقت به آن‌ها نمی‌رسد، تمیز شده است. ضمناً راه مطمئنی برای رفتن از اتاقی به اتاق دیگر است: اگر به اندازه‌ی کافی دیوار را دنبال کنید، بالاخره از هر دری رد می‌شوید.

ترفند این است که به‌جای فکر کردن با if، با خطا فکر کنید: سنسور کناری چقدر از جایی که می‌خواهم فاصله دارد؟ به تناسب همان خطا فرمان بدهید. خیلی نزدیک است، کمی دور شوید؛ خیلی دور است، کمی نزدیک شوید. اسم این کار کنترلر تناسبی است، ساده‌ترین و پرکاربردترین ایده‌ی کل مهندسی کنترل، و در پایتون چهار خط است.

کنترلر شماره ۲ — دنبال کردن دیوار سمت راست

# Wall following - hug the wall on the right at a steady distance.
TARGET = 30 # centimetres you want to keep on the right
GAIN = 0.8 # how hard the robot reacts to being off target
CRUISE = 20

if front < 35: # inside corner -> spin left, hard
 wheelleft, wheelright = -15, 15

else:
 error = (TARGET - frontright) * GAIN # positive = too close to the wall
 wheelleft = max(-25, min(25, CRUISE - error))
 wheelright = max(-25, min(25, CRUISE + error))

تنظیم GAIN تمام بازی است. خیلی کوچک باشد، ربات تنبلانه از دیوار دور می‌شود و گمش می‌کند. خیلی بزرگ باشد، به‌شدت زیگزاگ می‌رود و هر بار از هدف رد می‌شود — همان نوسان کلاسیکی که روی صفحه فوراً می‌بینید. از همین عدد شروع کنید، بعد آن را دو برابر یا نصف کنید و تغییر را تماشا کنید. عوض کردن یک ثابت در هر مرحله و دیدن نتیجه، خودِ مهندسی رباتیک است؛ میان‌بری وجود ندارد.

تنها کاری که دیوارگرد بلد نیست، تمیز کردن وسط اتاق است. اگر به حال خودش رها شود، کل مسابقه دور محیط می‌چرخد و امتیاز کمی می‌گیرد. به یک شریک نیاز دارد.

۳. مارپیچ — برای وسط بازِ اتاق

مارپیچ یعنی دایره‌ای که شعاعش در هر دور کمی بزرگ‌تر می‌شود. آن را در یک فضای باز شروع کنید تا در حلقه‌های تنگ به بیرون باز شود و یک ناحیه را تقریباً بی‌نقص و بدون رفت‌وبرگشت اضافی پوشش دهد. این دقیقاً مکمل دیوارگرد است: یکی لبه‌ها را می‌گیرد، دیگری وسط را.

کنترلر شماره ۳ — مارپیچ بازشونده

# The spiral - open circles that widen until something gets in the way.
GROWTH = 0.01 # how fast the circle opens up

if timer > 0: # bouncing away after a bump
 timer -= 1
 wheelleft, wheelright = -10, -10

elif front < 35: # the spiral ran into something
 inner = 0.15 # reset it, then bounce and re-spiral
 timer = 8

else:
 inner = min(1.0, inner + GROWTH) # every pass the circle opens a little
 wheelleft = 25
 wheelright = 25 * inner

کنار هم گذاشتن‌شان: یک کلید حالت

هیچ‌کدام از این سه رفتار به‌تنهایی کافی نیست. یک کنترلر رقابتی هر سه را نگه می‌دارد و بین‌شان جابه‌جا می‌شود — و منطق این جابه‌جایی هم فقط یک شمارنده‌ی دیگر است:

اسکلت استراتژی

# One robot, three behaviours, switched on a clock.
# Remember: ten passes make one second.
SPIRAL, WALL, BOUNCE = 0, 1, 2

mode_steps += 1

if mode == SPIRAL:
 do_spiral()
 if mode_steps > 130: # ~13 s of spiralling is plenty
 mode, mode_steps = WALL, 0

elif mode == WALL:
 do_wall_follow()
 if mode_steps > 290: # ~29 s along the wall, then shake it up
 mode, mode_steps = BOUNCE, 0

else:
 do_random_bounce()
 if mode_steps > 100:
 mode, mode_steps = SPIRAL, 0

این عددها حدسی هستند و باید هم باشند. عوضشان کنید، مسابقه را اجرا کنید، به امتیاز نگاه کنید و دوباره عوضشان کنید. تیم‌هایی خوب کار می‌کنند که پنجاه بار مسابقه را اجرا کرده‌اند، نه آن‌هایی که باهوش‌ترین پیش‌نویس اول را نوشته‌اند.

فقط U19: مسیریابی با GPS

ربات رده‌ی U19 یک GPS هم دارد که موقعیت دقیقش را در خانه گزارش می‌کند. این ماجرا را عوض می‌کند: به‌جای اینکه فقط به چیزی که جلوی رویتان است واکنش نشان دهید، می‌توانید تصمیم بگیرید که به یک نقطه‌ی مشخص بروید — گوشه‌ی دور، اتاقی که هنوز نرفته‌اید، یا یک پد شارژ.

رفتن به یک نقطه دو عدد لازم دارد: جهت از این‌جا تا هدف، و جهتی که الان رو به آن هستید. اختلاف این دو، خطای فرمان شماست. تنها تکه‌ی ریاضی واقعی این صفحه، همین است که چطور دو زاویه را بی‌خطر از هم کم کنیم.

چرا زاویه‌ها را نمی‌شود همین‌طوری کم کرد

فرض کنید رو به ۳۵۰ درجه ایستاده‌اید و هدف در ۱۰ درجه است. تفریق ساده ۳۴۰ درجه می‌دهد، پس ربات ساده‌لوح تقریباً یک دور کامل می‌چرخد — در حالی که جواب درست یک چرخش ۲۰ درجه‌ای به راست است. راه‌حل یک خط کلاسیک است: اختلاف را به سینوس و کسینوس تبدیل کنید و بگذارید atan2 دوباره آن را به زاویه برگرداند. نتیجه همیشه کوتاه‌ترین مسیر چرخش است، بین منفی پی و مثبت پی، و در نقطه‌ی بازگشت از ۳۵۹ به صفر هم نمی‌شکند.

کنترلر شماره ۴ — رفتن به یک مختصات (U19)

# U19 only: the robot knows where it is (x, y) and which way it faces.
# The app can drive there for you - or you can steer it yourself.

# --- the easy way: hand the job to the app ---------------------
goto(gx, gy) # start driving towards that point
if atgoal == 1: # becomes 1 once the robot has arrived
 stopgoto()

# --- the hand-written way, if you want to own the steering -----
# distto(gx, gy) distance to the point, in centimetres
# angleto(gx, gy) the turn you still need, -180..180 degrees
# (positive = the goal is to your left)

turn = angleto(gx, gy)

if distto(gx, gy) < 15: # arrived
 wheelleft, wheelright = 0, 0
elif turn > 12: # goal is to the left -> spin left
 wheelleft, wheelright = -12, 12
elif turn < -12: # goal is to the right -> spin right
 wheelleft, wheelright = 12, -12
else: # lined up -> drive at it
 wheelleft, wheelright = 25, 25

هشداری که برای تیم‌ها گران تمام می‌شود: تابع drive_to فقط خط مستقیم را دنبال می‌کند و بس. نمی‌داند که مبلی سر راه است. همیشه بررسی مانع را بالای آن نگه دارید — اگر سنسورهای جلو روشن شدند، سفر را رها کنید، فرار کنید و در دور بعد دوباره صادرش کنید. مسیریابی و دور زدن مانع دو کار متفاوت‌اند و هر دو باید سرِ پست باشند.

قبل از اینکه به عددهای GPS اعتماد کنید، بررسی کنید که واقعاً به همان ترتیب و واحدی که فکر می‌کنید برمی‌گردند؛ مقدار gps.getValues() را چاپ کنید و ربات را به یک گوشه‌ی مشخص ببرید. در رده‌ی U19 نقشه‌ی خانه ثابت است و فقط جای اشیا بین راندها عوض می‌شود، پس داشتن مجموعه‌ای از نقاط مسیر از پیش تعیین‌شده اشکالی ندارد — ولی کنترلری که فقط این نقاط را دنبال کند و سنسورهایش را نادیده بگیرد، کل مسابقه را صرف هل دادن یک صندلی می‌کند.

فقط U19: باتری و پدهای شارژ

ربات رده‌ی U19 با باتری کار می‌کند و ناحیه‌های سبز روی کف، پدهای شارژ بی‌سیم هستند. این موضوع مسابقه را به یک مسئله‌ی مدیریت منابع تبدیل می‌کند: هر ثانیه‌ای که صرف شارژ می‌شود، ثانیه‌ای است که تمیزکاری نمی‌شود، اما باتری خالی مسابقه‌ی شما را زودتر تمام می‌کند و همه‌ی امتیازهای بعد از آن لحظه را می‌گیرد.

پاییدن باتری و رفتن به نزدیک‌ترین پد

# U19 only: the battery drains as you drive and refills on the pad.
# battery is a percentage, 0..100. dockx / docky are the pad's coordinates.
LOW = 25 # go and charge below 25%

if battery < LOW:
 goto(dockx, docky) # charging beats cleaning right now
elif atgoal == 1 and battery > 90:
 stopgoto() # topped up - back to work
 clean()
else:
 clean() # your normal cleaning behaviour

دو عادت، استراتژی انرژی خوب را از بد جدا می‌کند. اول، زود بروید — خود سفر تا پد هم انرژی می‌خورد و تمام کردن باتری وسط راه بدترین اتفاق ممکن است. دوم، از روی عادت تا آخر شارژ نکنید. اگر مسابقه دارد تمام می‌شود، آن شارژ هیچ‌وقت خرج نخواهد شد؛ بیرون بمانید و تمیز کنید. خواندن زمان باقی‌مانده‌ی مسابقه و مقایسه‌اش با هزینه‌ی سفر، دقیقاً همان قضاوتی است که یک راند نزدیک را می‌برد.

امتیازدهی واقعاً چه چیزی را پاداش می‌دهد؟

این بخش را قبل از بهینه‌سازی هر چیزی بخوانید، چون تعیین می‌کند «بهتر» اصلاً یعنی چه. امتیاز بر اساس میزان تمیزکاری است: پوشش کاشی‌های کف و سطح پاکیزگی به‌دست‌آمده در زمان مسابقه. چند نتیجه مستقیماً از همین جمله بیرون می‌آید:

یک هدف ذهنی مفید، الگوی چمن‌زنی است: رفت‌وبرگشت‌های موازی و بلند در عرض اتاق که هر بار به اندازه‌ی پهنای ربات جابه‌جا می‌شوند. این همان کاری است که آدم‌ها موقع جاروبرقی کشیدن به‌طور غریزی می‌کنند و به پوشش بهینه خیلی نزدیک است. با همان IMU هم می‌شود تقریبی از آن ساخت: روی یک جهت ثابت برو تا دیوار، ۹۰ درجه بچرخ، یک پرش کوتاه برو، دوباره ۹۰ درجه به همان سمت بچرخ، و تکرار کن.

در تمام این مسیر جوانمردانه بازی کنید. قوانین، کد اصیل می‌خواهند — سرقت ادبی ممنوع است — و تصمیم داوران و برگزارکنندگان نهایی است. برداشتن یک ایده از این صفحه دقیقاً کاری است که این صفحه برایش نوشته شده؛ فرستادن کنترلر شخص دیگری نه.

رفع اشکال: چاپ کن، نگاه کن، هر بار یک چیز

نمی‌شود یک ربات را با دیباگر خط‌به‌خط جلو برد، چون دنیا مؤدبانه منتظر فکر کردن شما نمی‌ماند. ابزاری که جایش را می‌گیرد خط چاپ است — یک خط وضعیت کوتاه که چند بار در ثانیه چاپ می‌شود و می‌گوید ربات همین حالا چه باوری دارد:

تنها خط چاپی که ارزش نگه داشتن دارد

if step % 10 == 0:
 print("F", round(front), " L", round(left_side), " R", round(right_side),
 " mode", mode, " turning", turning)

و بعد این‌طور کار کنید:

  1. به عددها نگاه کنید، نه به ربات. وقتی چیزی خراب می‌شود، معمولاً کنسول از قبل جواب را نوشته است. اگر front عدد ۱۲۰۰ بوده و ربات با خیال راحت رفته توی دیوار، ثابت NEAR شما اشتباه است — نه منطقتان.
  2. کم چاپ کنید. سی خط در ثانیه نویزی است که خوانده نمی‌شود؛ step % 10 حدود سه خط می‌دهد که چشم انسان واقعاً می‌تواند دنبالش کند.
  3. هر بار فقط یک ثابت را عوض کنید. دو تا را عوض کنید و دیگر نمی‌دانید کدام کمک کرد. قبل از تغییر، مقدار قبلی را در یک کامنت بنویسید.
  4. سرعت مسابقه را کم کنید. برنامه با سرعت ۱، ۲، ۴ و ۸ برابر اجرا می‌شود — روی ۱ بگذارید و تماشا کنید. رفتاری که با سرعت کامل مثل لرزش تصادفی به نظر می‌رسد، با یک‌چهارم سرعت اغلب کاملاً واضح است.
  5. اول بازتولید، بعد رفع. اگر ربات در یک گوشه‌ی خاص گیر می‌کند، دوباره همان‌جا بگذاریدش و عددهای همان نقطه را ببینید، به‌جای حدس زدن از روی حافظه.

اشتباه‌هایی که همه مرتکب می‌شوند

نشانهعلت معمول
ربات اصلاً حرکت نمی‌کندwheelleft و wheelright را تنظیم نکرده‌اید، یا خطایی فایل را پیش از رسیدن به آن‌ها متوقف کرده — خط قرمز خطا زیر کادر کد را ببینید
یک شرط هیچ‌وقت اجرا نمی‌شودآستانه‌ی شما اشتباه است. پنل سنسورها را باز کنید و ببینید سنسور در آن لحظه واقعاً چه می‌گوید
ربات قفل می‌کندیک حلقه‌ی while یا sleep() داخل فایل شما. فایل باید تا آخر اجرا شود تا برنامه بتواند دوباره صدایش بزند
ربات کنار دیوار می‌لرزدشاخه‌ی «آیا دارم می‌چرخم؟» اول نیست، پس چرخش در هر دور از نو شروع می‌شود
چرخش تقریباً درست است — گاهیدارید زمان می‌شمارید به‌جای خواندن IMU. چرخش زمانی با کوچک‌ترین تغییر سرعت یا اصطکاک منحرف می‌شود
در راهرو مدام چپ و راست می‌رودمقدار GAIN دیوارگرد زیاد است — نصفش کنید
روی یک نقشه خوب کار می‌کند، روی نقشه‌ی بعدی نهثابت‌ها برای یک خانه تنظیم شده‌اند، یا مسیر هاردکد شده. نقشه هر راند عوض می‌شود
تغییرهای فایل هیچ اثری ندارندفایل ویرایش‌شده را پیش از زدن شروع دوباره بارگذاری نکرده‌اید
در تمرین امتیاز عالی، در مسابقه ضعیفهمیشه فقط از یک نقطه و یک زاویه‌ی شروع تست کرده‌اید

تمرین همین ایده‌ها بدون هیچ نصبی

اگر می‌خواهید پیش از دانلود هر چیزی حس حلقه‌ی کنترل را بچشید — یا معلم هستید و روی کامپیوترهای مدرسه اجازه‌ی نصب نرم‌افزار ندارید — همین مسابقه در مرورگر هم باز می‌شود. کاملاً داخل صفحه‌ی وب اجرا می‌شود: دو ربات، سنسورهای فاصله‌ی واقعی، یک سنسور رنگ که می‌گوید کف جلوی رو قبلاً تمیز شده یا نه، و همان حلقه‌ی «سنسور را بخوان، چرخ را تنظیم کن» که با پایتون نوشته می‌شود.

هر چیزی که بالا یاد گرفتید مستقیماً منتقل می‌شود. بلوک if timer > 0: در بازی همان الگوی شمارنده‌ی turning در کنترلر شماره ۱ است؛ دیوارگرد، مارپیچ و برخورد تصادفی هم آن‌جا کار می‌کنند — همان خانه، همان ربات و همان پایتون است، پس سریع‌ترین راه ممکن برای ساختن شهود است.

اجرای مسابقه در مرورگر خانه‌ی مسابقه، رده به رده

ساختن نقشه‌ی خودتان در مپ‌ساز، راهی دست‌کم‌گرفته‌شده برای تست کنترلر است. خانه‌ای طراحی کنید با یک راهروی باریک بدقلق، یک دسته پایه‌ی صندلی و یک اتاق بن‌بست؛ ضعف‌های الگوریتمتان را در چند دقیقه پیدا می‌کنید، به‌جای اینکه روز مسابقه کشفشان کنید.

سوالات پرتکرار

ربات جاروبرقی را با چه زبانی برنامه‌نویسی کنیم؟

پایتون. نمونه‌کدهای داخل ZIP پروژه — examples/robot_code_u14.py و robot_code_fs.py و robot_code_u19.py — فایل‌های ساده‌ی پایتون هستند و کل مسابقه را می‌شود فقط با متغیر، شرط، حلقه و تابع انجام داد. در رده‌ی U19 استفاده از زبان‌های پیشرفته‌تر هم مجاز است.

چقدر باید پایتون بلد باشم تا شروع کنم؟

متغیر، if/elif/else، حلقه‌ی while، لیست و تابع برای نوشتن یک کنترلر رقابتی کافی است. نه کلاس لازم است، نه دکوریتور، نه کتابخانه‌ی ریاضی خارجی. تقریباً همه‌ی کنترلرهای مبتدی کمتر از هشتاد خط هستند.

چرا ربات نمی‌تواند با time.sleep منتظر بماند؟

فایل شما یک بار اجرا می‌شود و تمام می‌شود؛ برنامه ده بار در ثانیه دوباره صدایش می‌زند. خوابیدن یا حلقه زدن داخل آن، ربات را قفل می‌کند و چرخ‌ها را با سرعت قبلی رها می‌کند. به‌جایش یک شمارنده مثل timer = 20 بگذارید و هر دور یکی از آن کم کنید.

چطور دقیقاً به اندازه‌ی چند درجه بچرخم؟

زمان نشمارید — IMU را بخوانید. زاویه‌ی yaw را قبل از چرخش ثبت کنید و تا وقتی کوتاه‌ترین اختلاف زاویه‌ای تا هدف کوچک نشده بچرخید. این اختلاف را با atan2(sin(target - actual), cos(target - actual)) حساب کنید تا در نقطه‌ی بازگشت زاویه هم درست بماند.

امتیاز چطور حساب می‌شود؟

میزان تمیزکاری: پوشش کاشی‌های کف و سطح پاکیزگی به‌دست‌آمده در زمان مسابقه. مسافت طی‌شده به‌تنهایی هیچ امتیازی ندارد، پس الگوریتمی که مدام از روی کف تمیزشده رد می‌شود امتیاز کمی می‌گیرد. تصویر کامل را در معرفی لیگ ببینید و برای اینکه امتیازهای واقعی چه شکلی بودند به جوایز و نتایج مسابقات سر بزنید.

می‌شود بدون نصب هیچ نرم‌افزاری همین مفاهیم را تمرین کرد؟

بله. نسخه‌ی مرورگری در همین سایت کاملاً داخل صفحه‌ی وب اجرا می‌شود و دقیقاً همان مدل ذهنی را دارد: سنسور بخوان، سرعت چرخ بده، یک دور در هر لحظه. این بازی یک رقابت جانبی است و لیگ اصلی نیست، اما سریع‌ترین راه برای حس کردن رفتار حلقه‌ی کنترل است.

نمونه‌کدها و نقشه‌ها را از کجا بگیرم؟

از مخزن رسمی: github.com/smart-home-league/simulation یا مستقیم از بخش شروع سریع.

وقتی گیر کردم کجا بپرسم؟

کانال تلگرام لیگ t.me/firasmarthome و سرور دیسکورد. سایت رسمی لیگ smarthomerobot.ir هم یک پایگاه دانش دارد.

قدم بعدی

حالا تصویر کامل را دارید: حلقه، سنسورها، چهار کنترلر کارکننده و یک راه فکر کردن درباره‌ی امتیاز. قدم بعدی خواندن بیشتر نیست — باز کردن برنامه است، عمداً خراب کردن کنترلر شماره ۱، و فهمیدن اینکه هر عدد چه کار می‌کند.

جوایز و نتایج مسابقات لیگ منتشر می‌شوند. از آن‌ها به‌عنوان معیار استفاده کنید: کنترلر خودتان را روی همان دنیاها اجرا کنید و ببینید کجا می‌ایستادید.

دانلود ابزارها و شروع لیگ ربات جاروبرقی چیست تمرین در مرورگر English version