ساخت یک ربات نمونه برای IWOR
(ویرایش ++C)

نویسنده: آیدین غریب‌نواز

فهرست


۱. مقدمه

استفاده از پیغام‌های متنی کار ساخت ربات را آسان نموده است. اما اگر قصد ساخت رباتی هوشمند را دارید، باید از تکنیک‌های هوش مصنوعی کمک بگیرید.

این متن نحوه ساخت یک ربات ساده را برای Imperfect world of Robots توصیف می‌کند. این ربات به طور اتفاقی به اطراف حرکت کرده و سعی می‌کند تا کریستال‌هایی که در اطرافش قرار دارد را شناسایی کند. این ربات هوشمند نبوده و نمی‌تواند بیش از یک دقیقه در این دنیای بی‌رحم زنده بماند! اما با دنبال کردن این آموزش می‌توانید نحوه کار IWOR را درک کنید و ایده‌هایی برای ساخت ربات‌های خود بدست آورید.

در این متن آموزشی، ما از socketهای یونیکس استفاده خواهیم کرد. شما می‌توانید از کدهای نوشته شده در این متن بدون تغییر در سیستم‌های مشابه یونیکس استفاده کنید. اگر از سیستم عامل دیگری استفاده می‌کنید (به خاطر دارید که ربات‌ها می‌توانند بر روی سیستم عامل‌های مختلف و دستگاه‌های مجزا اجرا شوند.) هنوز می‌توانید از این آموزش استفاده کنید. نحوه برنامه نویسی socketها را برای سیستم عامل خود پیدا کنید و این متن را از بخش سوم به بعد مطالعه کنید.

کد کامل این ربات را می‌توانید در دایرکتوری 'sample/robots/Tutorial' پیدا کنید.

توجه: کلیه کدهای درون این متن آموزشی و همچنین کدهای درون دایرکتوری 'sample/robots/Tutorial' تحت قوانین اجازه نامه عمومی گنو (GPL) منتشر شده‌اند. مطابق قوانین این اجازه نامه اگر شما از این کدها در ربات خود استفاده کنید، باید کدهای ربات خود را نیز تحت قوانین GPL منتشر کنید. به این معنی که دیگران اجازه ویرایش کد و انتشار مجدد آن را خواهند داشت. اگر چنین چیزی را نمی‌خواهید، لطفا از کدهای به کار رفته در این آموزش استفاده نکنید.


۲. اتصال به سرور

در ابتدا ربات باید به سرور شبیه ساز متصل شود. به طور پیش فرض، سرور به پورت ۷۲۰۰ گوش کرده و منتظر اتصال ربات‌ها می‌ماند. کاری که باید انجام شود باز کردن یک socket و اتصال آن به این پورت است. برای ساده کردن کارها ما از کلاس 'RobotSocket' در دایرکتوری 'sample/robots/Tutorial' استفاده خواهیم کرد.

'RobotSocket' سه متد دارد: سازنده, ()receive و ()sendMessage. سازنده (constructor) به دو آرگومان نیاز دارد. یکی برای شماره پورت و دیگری نام میزبان (host). اولین خطوط ربات ما به این شکل خواهد بود:

#include <iostream> 
#include "RobotSocket.hpp" 

RobotSocket* server;

int main(){
  //Connecting.
  server = new RobotSocket("localhost", 7200);
}

این کد یک socket ساخته و به سرور شبیه ساز که بر روی همان دستگاه (localhost) اجرا شده است، متصل می‌شود. متغیر 'server' را به صورت عمومی (global) تعریف کرده‌ایم تا دیگر متدها نیز بتوانند از آن برای ارسال و دریافت پیغام استفاده کنند.


۳. آغاز بازی

بعد از اتصال به سرور، ربات ما باید مشخصات خود را به سرور ارسال کند تا بتواند وارد بازی شود. این کار با ارسال دستور INIT صورت می‌گیرد.

#include <iostream> 
#include <string> 
#include "RobotSocket.hpp" 

//Global variables.
RobotSocket* server;

int main(){
  //Connecting.
  server = new RobotSocket("localhost", 7200);
  
  server->sendMessage("INIT&1&tutorial&0&");
  std::string initResult = server->receive();
  //Send INIT until success.
  while(initResult.compare("255") != 0){
    //Send it again.
    server->sendMessage("INIT&1&worker&0&");
    initResult = server->receive();
  }
}

دستور INIT سه آرگومان دارد. '1' شماره قبیله‌ای است که این ربات قصد پیوستن به آن را دارد. 'tutorial' نام ربات است و '0' کدی است که ربات باید به سرور ارسال کند تا اجازه حضور در دنیا را بدست آورد. در ابتدای بازی تعداد محدودی کد صفر وجود دارد. بعد از اتمام این کدها، ربات باید کد جدید را از والد خود کسب کند. بنابراین ایده خوبی است که قابلیتی را به این ربات اضافه کنید تا بتواند با استفاده از آرگومان‌های خط فرمان این کد را دریافت کند. در این صورت والدین می‌توانند به راحتی کدهایی را که از سرور دریافت کرده‌اند به فرزندان خود منتقل کنند. در این متن آموزشی ما در رابطه با نحوه تکثیر ربات‌ها بحث نخواهیم کرد. برای اطلاعات بیشتر به کتابچه راهنمای IWOR مراجعه کنید.

دلیل استفاده از حلقه while این است که گاهی اوقات سرور نمی‌تواند ربات را در فضای شبیه سازی شده قرار دهد. به عنوان مثال هنگامی که فضای خالی موجود نباشد. در این صورت پیغام 190 دریافت خواهد شد و ربات باید دوباره دستور INIT را ارسال کند. در صورت موفقیت 255 دریافت می‌شود. این ربات آنقدر دستور INIT را ارسال می‌کند تا موفق به ورود به بازی شود.

اکنون ربات ما به این دنیا وارد شده است! قدم بعدی حرکت کردن به اطراف است.


۴. حرکت اتفاقی به اطراف

در این بخش، ما متدی به نام randomMove به کد خود اضافه خواهیم کرد که اعداد اتفاقی تولید کرده و بر اساس آن در جهتی اتفاقی ربات را حرکت خواهد داد.

#include <cstdlib> 
#include <ctime>

//Constants
int const NORTH=0;
int const SOUTH=1;
int const EAST=2;
int const WEST=3;

void randomMove(){
  std::string moveCommand;
  
  //Change direction randomly.
  srand((unsigned)time(0));
  int rndint = int(4*(rand()/(float)RAND_MAX));
  if(rndint==NORTH){
    moveCommand = "MOVE&NORTH&";
  }else if(rndint==WEST){
    moveCommand = "MOVE&WEST&";
  }else if(rndint==SOUTH){
    moveCommand = "MOVE&SOUTH&";
  }else if(rndint==EAST){
    moveCommand = "MOVE&EAST&";
  }
  //Moving around ...
  server->sendMessage(moveCommand);
  server->receive();
}

برای تولید اعداد اتفاقی ما از تابع ()rand استفاده کرده‌ایم که از هدر cstdlib قابل دستیابی است. این تابع عددی بین صفر و RAND_MAX تولید می‌کند. اما ما به عددی بین صفر تا سه نیاز داریم. آن فرمول عجیب که در کد مشاهده می‌کنید این کار را برای ما انجام می‌دهد.

تابع rand به خودی خود اعداد واقعا اتفاقی تولید نمی‌کند. در حقیقت با هر بار اجرای برنامه همان اعدادی تولید خواهد شد که بار قبل تولید شده بود. اما خوشبختانه تابع ()srand نیز وجود دارد تا این مشکل را برای ما حل کند. این تابع یک عدد دریافت کرده و بسته به آن لیستی از اعداد تولید می‌کند که ()rand در میان آن حرکت کرده و اعداد را به ترتیب می‌خواند. با استفاده از یک آرگومان متغیر (به عنوان مثال در اینجا ساعت سیستم) می‌توانیم اعداد تقریبا اتفاقی تولید کنیم.

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


۵. پیدا کردن کریستال در اطراف ربات

حال که ربات ما می‌تواند به اطراف حرکت کند، قابلیت تشخیص کریستال‌های اطراف را به آن اضافه خواهیم کرد. ربات‌ها می‌توانند با استفاده از دستور SENSE از اشیای اطراف خود مطلع شوند. ما متد خود را ()seekForCrystals می‌نامیم.

#include <vector>

void seekForCrystals(){
  std::vector<std::string> args;
  std::vector<std::string>::iterator argsIterator;
  std::vector<std::string> objects;
  std::string senseResult;

  //Sniff around.
  server->sendMessage("SENSE&");
  //Getting the result.
  senseResult = server->receive();    

  //Extracting message arguments.
  int findResult;
  int index=0;
  int length;

  findResult = senseResult.find('&');
  while(findResult != -1){
    length = findResult - index;
    args.push_back(senseResult.substr(index,length));
    index = findResult+1;
    findResult = senseResult.find('&',index);
  }
  args.push_back(senseResult.substr(index));

  //Finding crystals in the result.
  std::string currentArg;
  argsIterator = args.begin();
  while(argsIterator != args.end()){
    currentArg = *argsIterator;

    //Extracting objects in this argument.
    index=0;
    objects.clear();
    findResult = currentArg.find(',');
    while(findResult != -1){
      length = findResult - index;
      objects.push_back(currentArg.substr(index,length));
      index = findResult+1;
      findResult = currentArg.find(',',index);
    }
    objects.push_back(currentArg.substr(index));

    if(objects.size() > 2){
      if(objects.at(2).compare("C")==0){
	//Printing out message.
	std::cout << "A crystal has been detected at ";
	std::cout << objects.at(0) << ",";
	std::cout << objects.at(1) << std::endl;
      }
    }
    ++argsIterator;
  }
}

هر چند این متد بسیار طولانی است، اما آنقدر که به نظر می‌رسد پیچیده نیست. در ابتدا ما دستور &SENSE را برای سرور ارسال کرده و نتیجه را بدست می‌آوریم. جواب ارسالی سرور چیزی همانند &#,3,5&C,4,6 است. به این معنی که در موقعیت (۳و۵) یک دیوار و نیز یک کریستال در (۴و۶) قرار دارد. همانطور که مشاهده می‌کنید اشیا با علامت & از یکدیگر جدا شده و موقعیت آنها نیز با ',' متمایز شده است. بنابراین تنها کاری که باید انجام دهیم استخراج اشیا از پاسخ ارسالی و پیدا کردن کریستال در میان آنها است. کد زیر اشیا را پیدا می‌کند:

findResult = senseResult.find('&');
  while(findResult != -1){
    length = findResult - index;
    args.push_back(senseResult.substr(index,length));
    index = findResult+1;
    findResult = senseResult.find('&',index);
  }
  args.push_back(senseResult.substr(index));

این کد کاراکتر & را پیدا کرده و رشته دریافتی را از این نقاط می‌شکند و نتیجه را در متغیر senseResult قرار می‌دهد. بعد از این کار، باید کریستال‌ها را در میان این اشیاء پیدا کنیم. برای این کار ابتدا کاراکتر ',' را پیدا کرده و همانند قبل رشته‌های اشیاء را از آن نقاط می‌شکنیم:

    findResult = currentArg.find(',');
    while(findResult != -1){
      length = findResult - index;
      objects.push_back(currentArg.substr(index,length));
      index = findResult+1;
      findResult = currentArg.find(',',index);
    }
    objects.push_back(currentArg.substr(index));

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


۶. سخن آخر

رباتی که ما نوشتیم بسیار بسیار ساده است، تنها می‌تواند به اطراف حرکت کرده و کریستال‌ها را تشخیص دهد. هیچ کدام از آنها را برنمی‌دارد، به میزان شارژ باتری خود اهمیتی نمی‌دهد (که باعث می‌شود به سرعت غیر فعال شود) و در حقیقت کار مفیدی انجام نمی‌دهد! اگر می‌خواهید از بازی به اندازه کافی لذت ببرید بهتر است ربات‌های پیچیده‌تری طراحی کنید. نگاهی به ربات‌های نمونه همراه این پروژه بیندازید تا ایده‌های دیگری بدست آورید.


Copyright (c) 2008 Aidin Gharibnavaz
کلیه حقوق برای آیدین غریب‌نواز محفوظ است. این سند تحت اجازه‌نامه مستندات آزاد گنو منتشر می‌شود. شما آزاد هستید تا این سند را ویرایش کرده و یا تحت قوانین مستندات آزاد گنو دوباره منتشر کنید. نسخه‌ای از این اجازه‌نامه در فایل FDL به همراه این مستند وجود دارد.

رنگ بندی کد‌ها در این صفحه با استفاده از نرم افزار Web C Plus Plus نسخه 0.8.4 انجام شده است.
Jeffrey Bakker دارنده حق کپی‌رایت webcpp بوده و آن را تحت قوانین GPL منتشر می‌کند.