PDA

توجه ! این یک نسخه آرشیو شده میباشد و در این حالت شما عکسی را مشاهده نمیکنید برای مشاهده کامل متن و عکسها بر روی لینک مقابل کلیک کنید : آموزش گام به گام برنامه‌نویسی سری 60 با ++c



mousa_mk
30-07-2007, 12:32
با عرض سلام

می‌خواهیم در این تاپیک برنامه‌نویسی موبایل را با یک مثال عملی آموزش دهیم. در حقیقت می‌خواهیم یک تیوتوریال را به صورت قدم به قدم پیش برویم.

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

تصویر این بازی رو می‌بینید:

http://mousa.persiangig.com/image/snake/normal1_fa.jpg

خود بازی را می توانید از لینک زیر دریافت نمایید:

دانلود بازی (http://mousa.persiangig.com/mobile_softwares/Snake.sis)

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

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

زبان برنامه‌نویسی مورد استفاده ما در اینجا ++C خواهد بود و از همین حالا خیال همه را راحت کنم که من زبان دیگری برای برنامه‌نویسی موبایل بلد نیستم. پس سؤال نفرمایید.

موضوع مهم دیگری که از همین حالا باید تذکر بدهم این است که در اینجا هدفمان آموزش خود برنامه‌نویسی نیست و فقط می‌خواهیم برنامه‌نویسی موبایل را با مثال عملی آموزش دهیم؛

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

فکر میکنم با این شرایط خوانندگان تاپیک محدودتر میشوند، ولی چاره دیگری نیست!

تا اولین بخش از آموزش خدانگهدار

mousa_mk
30-07-2007, 12:46
نرم‌افزارهای لازم برای شروع کار:

اولین برنامه‌ای که لازم داریم، ActivePerl است. خودمان به طور مستقیم، هیچ استفاده‌ای از این برنامه نخواهیم کرد ولی نرم‌افزارهای بعدی که معرفی خواهم کرد، از این برنامه برای کامپایل کردن استفاده خواهند کرد.

این برنامه را می‌توانید به طور رایگان از سایت زیر دانلود کنید. سایزش حدود 12 مگابایت است.
http://www.activestate.com

دومین برنامه SDK می‌باشد. SDK که مخفف عبارت Software Development Kit می‌باشد حاوی ابزارهایی مثل کامپایلر، دیباگر، شبیه‌ساز و ... می‌باشد و مهمترین نرم‌افزار ماست. ابتدا به این صفحه (http://developer.symbian.com/main/tools/sdks/s60/index.jsp) بروید و مشخصات گوشی که تصمیم دارید برایش برنامه بنویسید را ببینید. (مثلاً سری 60 ورژن 2 با FP 2)

حال باید SDK با این مشخصات را از جایی پیدا کنید. این بخش تقریباً سخت‌ترین قسمت ماجرا است. مرجع اصلی SDKها فروم نوکیا می‌باشد که البته این سایت هم ایران را ..... کرده است. (البته گاهاً بدون هیچ errorی اجازه دانلود می‌دهد ولی من خودم به شخصه تا بحال نتوانستم هیچ برنامه‌ای از فروم نوکیا دانلود کنم.) صفحه مخصوص SDKها در فروم نوکیا، اینجاست:
http://www.forum.nokia.com/info/sw.nokia.com/id/4a7149a5-95a5-4726-913a-3c6f21eb65a5/S60-SDK-0616-3.0-mr.html

من خودم SDK ورژن 2.1 را دارم و در سرور دانشگاه شریف آپلود کرده‌ام. اگر دنبال این ورژن هستید، می‌توانید از لینک زیر دریافت کنید: (ولی متأسفانه کیفیت و سرعت این سرور، بسیار پایین میباشد و فکر نکنم موفق به دانلود آن شوید!)
http://ce.sharif.edu/~moradi/download/mobile/S60_SDK_2_1_NET.zip

یک SDK برای سیمبین سری 60 ورژن 3 هم دوستانمان در سایت tarahi.net به همراه چند برنامه مفید دیگر برای برنامه‌نویسی سیمبین، آپلود کرده‌اند. اگر دنبال این SDK هستید، به لینک زیر مراجعه کنید:
http://www.tarahi.net/download.asp?dir=Symbian

با کمی سرچ کردن در اینترنت هم شاید بتوانید ورژن‌های دیگر SDK را هم گیر بیاورید.

اگر توانستید این مرحله سخت را پشت بگذارید، به سراغ سومین برنامه موردنیاز میرویم:
سومین برنامه مورد نیاز Microsoft Debugging Tools می‌باشد که باید از سایت زیر دریافت کنید:
http://www.microsoft.com/whdc/devtools/debugging

ورژنی از این برنامه را روی سرور دانشگاه آپلود کردهام که میتوانید از لینک زیر دریافت کنید: (حجم: 13 مگابایت)
http://ce.sharif.edu/~moradi/download/mobile/dbg_x86_6.5.3.8.exe

چهارمین برنامه Microsoft Visual Studio میباشد که هر ورژنی از آن را می‌توانید داشته باشد. البته از Visual Studio هم ما به صورت مستقیم استفاده نخواهیم کرد ولی برای کار کردن ابزارهای دیگر لازم است.

پنجمین و باحالترین برنامه هم که تقریباً تمام کارمان با آن خواهد بود، Borland C++BuilderX Mobile Edition(v1.5) است. این برنامه قبلاً رایگان بود و به راحتی می‌شد از سایت بورلند دانلود کرد ولی الان مدتی است که اجازه دانلود نمی‌دهد؛ با این که هنوز هم رایگان است!

این برنامه را هم آپلود کرده‌ام که میتوانید از لینک زیر دریافت کنید:
http://ce.sharif.edu/~moradi/download/mobile/CBX1.5_mobile-RTM.zip
فایل فعالسازی اش را هم از اینجا بگیرید:
http://ce.sharif.edu/~moradi/download/mobile/reg591.txt

ضمناً به اسم برنامه توجه داشته باشید. این برنامه را با برنامه Borland C++ Builder اشتباه نگیرید. این IDE که ما با آن کار خواهیم کرد، فقط و فقط مختص موبایل می‌باشد.

ضمناًدر صورت علاقمندی میتوانید به جای این برنامه آخری، از IDEهای دیگری هم مثل CodeWarrior، Visual Studio، Eclipse و ... هم استفاده کنید که من چون تا بحال دنبال این برنامه‌ها نبودم، تجربه‌ای هم ندارم و کمک زیادی هم نمی‌توانم در این مورد بکنم.

mousa_mk
30-07-2007, 12:53
امیدوارم که برنامه‌ها را به دست آورده باشید. از حالا درسمان را به صورت عملی شروع می‌کنیم.

ایجاد پروژه جدید
نرم‌افزار C++BuilderX را اجرا کنید. از منوی File مورد New… را بزنید. در پنجره‌اي كه باز مي‌شود، در بخش سمت چپ، "Series 60" را انتخاب كرده و سپس در سمت راست، "New Series 60 GUI Application" را برگزينيد و OK را بزنيد. در پنجره جديد، نام پروژه را snake بگذاريد، مسير ذخيره شدن پروژه را انتخاب كنيد و ضمناً مورد "Create project subdirectory" را تيك بزنيد. SDK موردنظر را هم مشخص كنيد. شكل زير:

http://mousa.persiangig.com/image/others/MobilestanTutorial/pic01.JPG

Next را بزنيد تا به صفحه بعدي برويد. دوباره همان نام snake را وارد كنيد. در كادر UID هم يك عدد در مبناي 16 (هگزادسيمال) به صورت تصادفي بين 0x10000000 و 0x1FFFFFFF وارد كنيد. در پايان كار اگر خواستيد برنامه‌تان را براي عموم پخش كنيد، بايد اين عدد را به عدد معتبري (كه بالاي 0x20000000 مي‌باشند) تغيير دهيد و اين اعداد معتبر را هم بايد از سايت www.symbiansigned.com تهيه كنيد. (در فرصت مناسب درباره UID توضيح خواهيم داد.)

براي view type مورد full screen را انتخاب كنيد. و بالاخره finish را بزنيد تا پروژه‌مان ايجاد شود.
تصوير محيط كاري را به صورت زير خواهيد ديد:

http://mousa.persiangig.com/image/others/MobilestanTutorial/pic02.JPG

قبل از اين كه كار ديگري انجام دهيم، بهتر است پروژه را يكبار امتحان كنيم. دكمه Run Project (تصوير زير) را بزنيد تا برنامه اجرا شود. البته كامپايل كردن و ساختن برنامه مدتي طول مي‌كشد. كمي صبر كنيد.

http://mousa.persiangig.com/image/others/MobilestanTutorial/pic03.JPG

يكي از اشكالزاترين مراحل همين اولين اجرا است. خيلي از افراد در اولين اجراي برنامه با حدود 20 – 30 تا error برخورد مي‌كنند و نمي‌دانند كه چه كنند. معروفترين اين errorها همين به صورت زير هستند:


'nmake' is not recognized as an internal or external error
'abld' is not recognized as an internal or external error
'link.exe' is not recognized as an internal or external error
cannot locate env32.pm
...

در صورتي كه شما هم با errorهاي بالا (يا خطاهاي ديگري) مواجه شديد، اول پيشنهاد مي‌كنم كه به تاپيك «خطا در اولين اجراي برنامه (http://irsymbiandevnet.fullboards.com/ENaCaa-aa1740O1740-O1740aE1740a-c2/SDKaC-IDEaC-a-CEOCNaC1740-I1740N-f18/Borland-CBuilderX-f22/IOC-IN-CINCi-Caaia-ENaCaa-t7.htm)» در فروم خودم مراجعه كنيد و ببينيد كه آيا راه‌حل‌هايي كه قبلاً پيشنهاد شده‌اند، برايتان جوابگو هستند يا نه.
در صورت حل شدن مشكل، خدا را شكر كنيد و كارتان را ادامه دهيد؛ ولي در غير اين صورت errorتان را در همان تاپيك مذكور و يا در اينجا مطرح كنيد تا من يا دوستان ديگر راهنمايي‌تان كنيم.

اگر همه چيز به درستي پيش برود، بعد از ساخته شدن پروژه، شبيه‌ساز اجرا مي‌شود و منوي اصلي را نشان مي‌دهد. با كليدهاي جهت خود شبيه‌ساز به انتهاي منو برويد. برنامه‌مان را با نام SNAKE در آنجا خواهيد ديد. آن را اجرا كنيد. برنامه‌مان اجرا خواهد شد و يك صفحه سفيد خالي نشان داده خواهد شد. (تصوير زير)

http://mousa.persiangig.com/image/others/MobilestanTutorial/pic04.JPG

با زدن كليد راست شبيه‌ساز از برنامه خارج شويد و شبيه‌ساز را ببنديد.
هميشه بعد از اجرا كردن برنامه وقتي مي‌خواهيد به كد زدن ادامه دهيد، شبيه‌ساز را ببنديد. اگر شبيه‌ساز باز بماند و دوباره بخواهيد برنامه را اجرا كنيد،errorهاي عجيب و غريبي دريافت خواهيد كرد.

mousa_mk
30-07-2007, 13:00
قبل از اين كه شروع به كد زدن كنيم، ابتدا بايد مي‌خواهيم ببينيم IDE چه چيزهايي برايمان ايجاد كرده است. در حقيقت IDE خيلي از كلاس‌هايي كه حتماً بايد داشته باشيم را به همراه تعدادي فايل ديگر از جمله فايل‌هاي resource، mmp، bld.inf و ... برايمان ايجاد كرده است. اين فايل‌ها را به طور خلاصه توضيح مي‌دهيم. (توجه: شرح مفصل اين فايل‌ها را در مقاله «فايل‌هاي برنامه‌نويسي سيمبين (http://www.mousa.persiangig.com/articles/S60_files.pdf)» و همچنين در مقاله «آشنايي با برنامه‌نويسي سيمبين سري 60 (http://mousa.persiangig.com/articles/S60GettingStarted_Farsi.zip)» نوشته‌ام.)

در پنجره Project كه در سمت چپ پنجره اصلي برنامه C++BuilderX است، مي‌توانيد فايل‌هاي پروژه را ببينيد. تصوير زير:

http://mousa.persiangig.com/image/others/MobilestanTutorial/pic05.JPG

(ابتدا توجه داشته باشيد كه فايل snake.cbx كه در اين پنجره در بالاترين سطح قرار دارد، فايل استاندارد سيمبين نيست و برنامه C++BuilderX آن را براي خود ايجاد كرده است. محتويات اين فايل به صورت xml مي‌باشند كه البته ما با آن هيچ كاري نداريم و خود IDE از آن استفاده مي‌كند.)

1) فايل bld.inf: اولين و اصلي‌ترين فايل پروژه، فايل bld.inf مي‌باشد. در داخل اين فايل فقط نام فايل‌هاي mmp استفاده شده در پروژه نوشته مي‌شوند. البته اطلاعات محدود ديگري هم در اين فايل در برنامه‌هاي متفاوت‌تر وجود دارند ولي كاري با آنها نداريم. اگر اين فايل را باز كنيد، كد زير را در آن خواهيد ديد:



// Symbian OS BLD.INF Project file
// Generated by Borland C++Builder Project Wizard

PRJ_MMPFILES
snake.mmp


در اينجا ما فقط يك فايل mmp داريم كه نام آن در اينجا ثبت شده است.
2) فايل mmp: اين فايل، فايل تعريف مشخصات برنامه است. تمامي ويژگي‌هاي برنامه، فايل‌هاي استفاده شده، منابع برنامه، فايل‌هاي خارجي استفاده شده، libraryهاي استفاده شده، كد زبان‌هاي استفاده شده و خيلي چيزهاي ديگر، در اين فايل ثبت مي‌شوند. مي‌توانيد اين فايل را باز كنيد و محتوايتش را نگاه كنيد.

سورس‌فايل‌هايي هم كه ما در پروژه استفاده خواهيم كرد، در اين فايل ثبت شده‌اند و البته فايل‌هايي هم كه ما بعداً اضافه خواهيم كرد، بايد در اينجا نامشان نوشته شود.

3) فايل snake.rss: فايل‌هاي rss مخصوص resourceها يا همان فايل‌هاي منبع مي‌باشند. در فايل‌هاي resource مواردي مثل منوهاي برنامه، dialog boxهاي برنامه و ...، ساختارشان مشخص مي‌باشد. البته در برنامه C++BuilderX ما مي‌توانيم به صورت تصويري و خيلي راحتتر، منوها و dialogها را طراحي كنيم و نيازي به دانستن ساختار خشك و خالي اين فايل‌ها نداريم، به طوري كه اگر اين فايل را باز كنيد، مي‌بينيد كه IDE، كامنتي با اين مضمون نوشته است كه «اين كد توسط IDE مديريت مي‌شود. آن را ويرايش نكنيد!»؛ ولي در سطح بالاتر و همچنين براي كار كردن در مواقعي كه IDE در اختيار نداريم، مجبوريم كه ساختار اين فايل‌ها را بلد باشيم. البته در اينجا در مورد ساختار اين فايل‌ها توضيحي نمي‌دهيم و از ابزارهاي گرافيكي IDE استفاده خواهيم كرد. (براي توضيح بيشتر به دو مقاله معرفي شده در بالا، مراجعه كنيد.)

mousa_mk
30-07-2007, 13:01
در ادامه مطلب قبلي، اكنون كلاس‌‌هاي برنامه را توضيح مي‌دهيم:

قبل از توضيح كدها، بايد كلاس‌هايي كه هر برنامه سيمبين بايد داشته باشد را توضيح مي‌دهيم:
1) كلاس Application: اين كلاس، كلاس اصلي برنامه است و آبجكتي از اين كلاس، معادل خود برنامه است. اين كلاس بايد از كلاس CAknApplication مشتق شود. يكي از وظايف اصلي اين كلاس، ساخت آبجكتي از كلاس document است.

2) كلاس Document: اين كلاس مسئول document برنامه است. در سيمبين اين محدوديت را داريم كه هر برنامه در حال اجرا فقط يك document مي‌تواند داشته باشد. اين كلاس هم بايد از CAknDocument مشتق شود. از وظايف اصلي اين كلاس، ياخت آبجكتي از كلاس AppUi مي‌باشد.

3) كلاس AppUi: اين كلاس براي مديريت رويدادها، بستن فايل‌ها و ... استفاده مي‌شود. هيچ خروجي در صفحه ندارد و رابط صفحه نمايش با Viewهاي خودش است. كلاس پايه‌اش CAknAppUi يا CAknViewAppUi است. (بسته به معماري برنامه)

4) كلاس View: هر View يك نمايش در صفحه است. هر View يك حاوي يك آبجكت از كلاس Container مي‌باشد كه Container خود به صورت مستقيم مسئول صفحه نمايش است. (در برخي معماري‌ها View خودش مسئول صفحه نمايش است و ديگر هيچ Containerي ندارد!) از كلاس CAknView مشتق مي‌شود.

5) كلاس Container: همانطور كه گفته شد، مسئول صفحه نمايش مي‌باشد. از كلاس‌هاي CCoeControl و MCoeControlObserver مشتق مي‌شود. (در سيمبين كلاس‌هايي كه نامشان با M شروع مي‌شود، فقط حاوي متد هستند و هيچ متغير عضو و ... ندارند. در حقيقت دقيقاً معادل با مفهوم Interface در جاوا مي‌باشند. با اين توضيحات مي‌توان ادعا كرد كه كلاس Container فقط از CCoeControl مشتق شده است.)

mousa_mk
30-07-2007, 13:03
حال به خود كدها مي‌پردازيم:

از قسمت source در پنجره project فايل snakemain.cpp را باز كنيد. اين فايل، فايل اصلي بخش برنامه‌نويسي مي‌باشد و اجراي برنامه از اينجا شروع مي‌شود. اين فايل، حاوي دو تابع مي‌شود كه IDE خود، آنها را براي ما پياده‌سازي كرده است. اولي تابع E32Dll() مي‌باشد. اين تابع نقطه ورودي dll مي‌باشد و هميشه بايد مقدار KerrNone را برگرداند. (هر برنامه در سيمبين، حالت خاصي از يك dll است.)

دومين تابع، NewApplication مي‌باشد. اين تابع يك آبجكت از كلاس Application ما مي‌سازد و اشاره‌گر به آن را برمي‌گرداند. FrameWork با صدا كردن اين تابع، آبجكتي از برنامه ما به دست مي‌آورد و بقيه كارها را روي آبجكت انجام مي‌دهد.

فايل snakeapp.h را باز كنيد. (براي باز كردن اين فايل، ابتدا فايل snakeapp.cpp را باز كنيد و از پنجره Structure در سمت چپ، از بخش Includes، اين فايل را باز كنيد.)

كلاس Application در اينجا تعريف شده است. همانطور كه مي‌بينيد در اين هم فقط دو تابع پياده‌سازي شده است كه يكي از آنها يك آبجكت از كلاس Document مي‌سازد و اشاره‌گر به آن را برمي‌گرداند و دومي هم UID برنامه را برمي‌گرداند. (مي‌توانيد پياده‌سازي اين دو تابع را در فايل cppاش ببينيد.)

فايل snakedocument.h را باز كنيد. در اين فايل هم كلاس Document برنامه تعريف شده است. در اين كلاس هم تعدادي تابع Override شده‌اند كه توضيح مي‌دهم:

1) Constructor و Destructor كه البته نيازي به توضيح ندارند.
2) دو تابع استاتيك با نام‌هاي NewL و NewLC: وظيفه اين توابع ساخت آبجكتي از كلاس مي‌باشد.
3) تابع ConstructL: اين تابع هم توسط دو تابع استاتيك اشاره شده صدا زده مي‌شود و بقيه كار آنها را انجام مي‌دهد. (توضيح بيشتر در مورد اين تابع و دو تابع بالا را در پست بعدی خواهم داد.)

4) CreateAppUiL كه وظيفه‌اش ساخت آبجكتي از كلاس AppUi و برگرداندن اشاره‌گر به آن است.

توابع كلاس‌هاي AppUi و View و Container را به علت اين كه زياد هستند، الان شرح نمي‌دهم ولي بعداً در جاهايي كه لازم خواهند شد، توضيح خواهم داد.

mousa_mk
30-07-2007, 13:05
اگر به كلاس Document نگاهي دوباره بيندازيد، خواهيد ديد كه ما constructor اين كلاس را private كرده‌ايم و در عوض براي ساختن آبجكت از اين كلاس از تابع استاتيك NewL استفاده مي‌كنيم. به نظر شما علت اين كار چيست؟

در جواب اين سؤال بايد يكي از تكنيك‌هاي مهم در برنامه‌نويسي سيمبين را كه با نام «سازنده دو فازي» شناخته مي‌شود، توضيح دهم:

در برنامه‌نويسي موبايل برخلاف كامپيوتر كه فكرمان از مقدار حافظه فارغ است، هميشه دغدغه كمبود حافظه داريم و بايد به همين دليل در مديريت حافظه حداكثر توجه‌مان را داشته باشيم.
پردازش استثنا يا همان Exception Handling در سيمبين نسبت به ++C استاندارد كاملاً متفاوت است. در سيمبين اگر هنگام اجراي تابعي، يك Exception رخ دهد، اجراي تابع متوقف مي‌شود و كنترل برنامه به جايي كه تابع صدا زده شده است، برمي‌گردد. و تاجايي پيش مي‌رود كه توسط يك TRAP هندل شود. در اين حالت مي‌گوييم كه «تابع تَرك شده است» (يك leave رخ داده است.) به توابعي كه پتانسيل ترك شدن (leave شدن) دارند، leaving functions مي‌گويند. توابعي كه پتانسيل leave شدن دارند، عبارتند از:

1) توابعي كه براي ايجاد متغير يا آبجكتي، حافظه مي‌گيرند. زيرا ممكن است با كمبود حافظه مواجه شوند.
2) توابعي كه تابع ديگري با اين شرط را صدا مي‌زنند.
3) توابعي كه در آنها خود برنامه‌نويس با نوشتن دستوراتي مانند User::Leave() يا User::LeaveIfError() باعث ايجاد يك Leave مي‌شود.

مي‌دانيم كه هر كلاسي معمولاً داراي تعدادي اشاره‌گر است كه بايد در هنگام ساخته شدن آبجكتي از آن كلاس، بايد متغيرهاي جديد ساخته شوند و آدرسشان در اين اشاره‌گرها ذخيره شود. و نيز مي‌دانيم كه اين وظيفه به عهده Constructor كلاس است. اما در اين صورت، با توجه به مطالبي كه در بالا گفتيم، Constructorكلاس، داراي پتانسيل leave مي‌شود، يعني اين كه مي‌تواند leave شود. ولي اين يك اتفاق بسيار ناگوار است. زيرا leave شدن Constructor معادل با ناتمام ساخته شدن آبجكت است.

يكي از نكات اساسي در سيمبين اين است كه هيچ Constructor و هيچ Destructorي نبايد leave شوند.

حال براي حل اين مشكل چه بايد كرد؟

يك راه‌حل كه در بادي امر به ذهن مي‌رسد، اين است كه وظيفه مقداردهي اوليه كردن اشاره‌گرهاي كلاس را در يك تابع ديگري غير از Constructor بنويسيم و از كاربر بخواهيم كه بعد از ساختن آبجكتي از كلاس، اين تابع را هم صدا بزند. ولي اين راه‌حل استاندارد نيست، زيرا مثلاً ممكن است كاربر فراموش كند كه اين كار را انجام دهد. راه‌حل ديگري كه به ذهن مي‌رسد، اين است كه همين تابع را خودمان در Constructor صدا بزنيم كه در اين صورت دوباره مشكل اول پيش مي‌آيد. زيرا در اين صورت Constructor ما طبق شرط (2) دوباره پتانسيل leave شدن پيدا مي‌كنند.
راه‌حل اين مسئله اين‌گونه است: ابتدا Constructor را private مي‌كنيم تا نتوان با new كردن از كلاس آبجكت گرفت. در خود Constructor فقط كدهاي غيرقابل leave را مي‌نويسيم. سپس يك تابع استاتيك (معمولاً با نام NewL) مي‌نويسيم تا ساخت آبجكت توسط اين تابع انجام شود. در اين تابع، ابتدا يك آبجكت new مي‌كنيم. سپس اين آبجكت را در استك Cleanup قرار مي‌دهيم. (اين مورد را بعداً توضيح مي‌دهم.) سپس تابعي كه براي مقداردهي اشاره‌گرها و رفرنس‌ها نوشته‌ايم را صدا مي‌زنيم. (معمولاً با نام ConstructL) بعد آبجكت را از استك pop مي‌كنيم و با كاربر برمي‌گردانيم.

و اما Cleanup Stack چيست؟ Cleanup Stack، استكي است كه سيمبين مواظب اعضاي آن است. اگر آبجكتي كه داخل اين استك است، هيچ اشاره‌گري به آن وجود نداشته باشد، توسط سيستم عامل، delete خواهد شد.

پس توضيح نحوه كار NewL به اين صورت خواهد بود: ابتدا آبجكتي از كلاس ايجاد مي‌شود. در اين مرحله چون هيچگونه حافظه‌اي براي متغيرها نمي‌گيريم، امكان leave شدن وجود ندارد. سپس همين آبجكت را به Cleanup Stack اضافه مي‌كنيم. (push مي‌كنيم) در اينجا با صدا زدن تابع ConstructL حافظه مورد نياز براي آبجكت را مي‌گيريم. در اين مرحله امكان leave شدن وجود دارد. اگر leaveي اتفاق افتد، آبجكت موجود در استك، چون اشاره‌گر به آن از بين مي‌رود، توسط سيستم عامل حذف مي‌شود و هيچ گونه حافظه‌اي تلف نمي‌شود. در صورتي كه اجراي تابع ConstructL با موفقيت تمام شود، آبجكت از استك، pop مي‌شود و به كاربر برگردانده مي‌شود.
ضمناً براي بهتر كردن ماژولار بودن كد، ايجاد كردن و push كردن آبجكت را در تابع NewLC مي‌نويسند و صدا زدن تابع ConstruclL و pop كردن آبجكت را برعهده تابع NewL مي‌گذارند. دو تابع NewL و NewLC فاز اول ساختن آبجكت و ConstructL هم فاز دوم ساختن آن را انجام مي‌دهد.

استفاده كردن از اين روش تقريباً در تمام كلاس‌هاي سيمبين انجام مي‌شود و تبديل به يك روش كلي شده است. حتي در كلاس‌هايي كه هيچ متغيري نداريم و امكان leave شدن وجود ندارد، باز هم از اين روش استفاده مي‌شود. مثلاً شما در همين كلاس Document و خيلي از كلاس‌هاي ديگر مي‌بينيد كه تابع ConstructL خالي است و كاري انجام نمي‌دهد.

تنها نكته‌اي كه ذكر آن باقي مي‌ماند، اين است كه قبل از ساخته شدن آبجكتي از كلاس Application برنامه، هنوز Cleanup Stack به وجود نيامده است و بنابراين براي كلاس Application نمي‌توان از روش سازنده دوفازي استفاده كرد.

مطلب اين پست، تماماً مفهومي بود و مطمئنم كه خسته‌كننده بوده است، ولي بايد توضيح داده مي‌شود. همين مفاهيم «سازنده دو فازي» و «Cleanup Stack»، دو مفهوم بسيار مهم و بسيار كاربردي در برنامه‌نويسي سيمبين مي‌باشد كه حتماً بايد بلد باشيد.

mousa_mk
30-07-2007, 13:09
با عرض معذرت از تمامی دوستان، به علت اینکه من این هفته پنجشنبه امتحان میان ترم دارم، شاید تا شنبه نتونم خدمتتون برسم.

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

mousa_mk
04-08-2007, 11:25
اكنون مي‌خواهيم كد زدن براي بازي را شروع كنيم.

هر بازي (موبايل يا كامپيوتر) يك Engine دارد كه تمام كارهاي بازي بر عهده آن مي‌باشد. ما هم براي بازيمان يك Engine طراحي مي‌كنيم. از منوي File مورد New File… را بزنيد. در پنجره‌اي كه باز مي‌شود، براي نام فايل، عبارت SnakeGameEngine و براي نوع فايل هم مورد h را برگزينيد. مسير فايل را به پوشه inc تغيير دهيد و تيك Add saved file to project را برداريد. (تصوير زير)

http://mousa.persiangig.com/image/others/MobilestanTutorial/pic06.JPG

OK را بزنيد تا فايل ايجاد شود.
كد زير را در فايل كپي كنيد:


#ifndef __MKSNAKEGAMEENGINE_H_
#define __MKSNAKEGAMEENGINE_H_

#include <e32base.h> // Class 'CBase' is here
#include <e32math.h> // Math functions. (We use 'rand' here)
#include <s32file.h> // For working with file server
#include <aknutils.h>
#include "Snake.h"
#include <w32std.h>
#include <fbs.h>
#include <aknnotewrappers.h> // Message boxes are here
#include <gdi.h>
#include <e32std.h>
#include <eikenv.h>
#include "MKSnake.rsg"



#define SCREEN_WIDTH 176 // Screen width in S60 display
#define SCREEN_HEIGHT 208 // Screen height in S60 display
#define SCREEN_EDGE 3

#define DELETEANDNULL(p) {delete p; p = NULL;}

#define GAME_VERSION_NUMBER 1 // Game version
_LIT(KMKSnakeDataFile, "settings.dat"); // File for saving highscore



const TInt KTickInterval = 150000; // '0.15 Second' Interval for the Timer


class CMKSnakeContainer; // Forward declaration


class CMKSnakeGameEngine : public CBase
..
public:
// -------------------------------------------------------------------------
// 'NewL' function for creating an Instance of this class
// -------------------------------------------------------------------------
static CMKSnakeGameEngine* NewL(CMKSnakeContainer* aOwningControl);

// -------------------------------------------------------------------------
// This function is called by 'NewL' function
// -------------------------------------------------------------------------
static CMKSnakeGameEngine* NewLC(CMKSnakeContainer* aOwningControl);

// -------------------------------------------------------------------------
// Destructor of class
// -------------------------------------------------------------------------
~CMKSnakeGameEngine();

// -------------------------------------------------------------------------
// This function drows objects in the screen by suitable state
// -------------------------------------------------------------------------
void DrawScreen(const TRect& aRect);

// -------------------------------------------------------------------------
// This function starts a new game
// -------------------------------------------------------------------------
void StartNewGame();

// -------------------------------------------------------------------------
// This function creates and starts the Timer
// -------------------------------------------------------------------------
void StartTimerL();

// -------------------------------------------------------------------------
// This function stops our timer
// -------------------------------------------------------------------------
void StopTimer();

// -------------------------------------------------------------------------
// This function gets some properties and draws a text
// -------------------------------------------------------------------------
void DrawText(TInt aResourceId, TRect aRect, TRgb aColor, const CFont* aFont,
CGraphicsContext::TTextAlign aHoriz, TInt aLeftMrg=0);

// -------------------------------------------------------------------------
// This function notifies the engine that the state has been changed
// -------------------------------------------------------------------------
void StateChanged();

// -------------------------------------------------------------------------
// This function notifies the engine that the snake has been died
// -------------------------------------------------------------------------
void SnakeDied();

// -------------------------------------------------------------------------
// This function saves the highscore in a file
// -------------------------------------------------------------------------
void SaveGameProgressL();

// -------------------------------------------------------------------------
// This function loades the highscore form file
// -------------------------------------------------------------------------
void LoadGameProgressL();

// -------------------------------------------------------------------------
// This function draws the random rectangle in the screen
// -------------------------------------------------------------------------
void DrawRandPoint();

// -------------------------------------------------------------------------
// This function creates a random point
// -------------------------------------------------------------------------
void CreateRandPoint();

// -------------------------------------------------------------------------
// This function notifies the engine that the snake has gotten the random point
// -------------------------------------------------------------------------
void GotTheRandPoint();

// -------------------------------------------------------------------------
// Draws inofrmation such that score, in the upper rectangle
// -------------------------------------------------------------------------
void DrawInformation();

private:
// -------------------------------------------------------------------------
// Private Constructor for this class
// -------------------------------------------------------------------------
CMKSnakeGameEngine(CMKSnakeContainer* aOwningControl);

// -------------------------------------------------------------------------
// This function creates the member variables of class
// -------------------------------------------------------------------------
void ConstructL();

// -------------------------------------------------------------------------
// This function draws two rectangles in the screen
// -------------------------------------------------------------------------
void DrawRects();

// -------------------------------------------------------------------------
// This function resets the screen
// -------------------------------------------------------------------------
void DrawInits(const TRect& aRect);

// -------------------------------------------------------------------------
// This function is called by Time
// -------------------------------------------------------------------------
static TInt TimerCallBack(TAny* aObject);

// -------------------------------------------------------------------------
// This function has being called by the timer
// -------------------------------------------------------------------------
void DoCallBack();

public:
// -------------------------------------------------------------------------
// The owner of this class's one object
// -------------------------------------------------------------------------
CMKSnakeContainer* iOwningControl;

// -------------------------------------------------------------------------
// A pointer to the snake object
// -------------------------------------------------------------------------
CSnake* iSnake;


// -------------------------------------------------------------------------
// The periodic timer that moves the snake
// -------------------------------------------------------------------------
CPeriodic* iPeriodicTimer;

// -------------------------------------------------------------------------
// An enumeration for determining the game's state
// -------------------------------------------------------------------------
enum TGameState
..
ENotStarted,
EPaused,
ENormal,
EEnded
};

// -------------------------------------------------------------------------
// This object determines the game's state
// -------------------------------------------------------------------------
TGameState iGameState;

// -------------------------------------------------------------------------
// This is the random point
// -------------------------------------------------------------------------
TPoint iCurRandPoint;

// -------------------------------------------------------------------------
// A time object for creating a random
// -------------------------------------------------------------------------
TTime iTime;

// -------------------------------------------------------------------------
// This is the highscore
// -------------------------------------------------------------------------
TInt iMaxSnakeSize;


// -------------------------------------------------------------------------
// These objects is for drawing the screen:
// -------------------------------------------------------------------------
CFbsBitmap* iBackBufferBmp;
CFbsBitGc* iGc;
CFbsBitmapDevice* iBackBufferBmpDrawingDevice;

};

#endif


همانطور كه خودتان هم مي‌دانيد، اين كد، مربوط به تعريف كلاس Engine بود. در كد بالا، تمام متدها و متغيرها را به طور خلاصه توسط كامنت توضيح داده‌ام. حال بايد اين كلاس را پياده‌سازي كنيم. دوباره از منوي File، مورد New File… را بزنيد. نام فايل را دوباره SnakeGameEngine بگذاريد. نوع فايل را cpp تعيين كنيد و مسير ذخيره شدن فايل را به پوشه src تغيير دهيد. مورد add saved file to project نبايد تيك داشته باشد. (شكل زير)

http://mousa.persiangig.com/image/others/MobilestanTutorial/pic07.JPG

OK را بزنيد. در پنجره project در سمت چپ روي sources راست‌كليك كنيد و add sources… را بزنيد و فايلي كه الان ايجاد كرديد، را انتخاب كرده و OK را بزنيد تا به پروژه اضافه شود.
در اين فايل، ابتدا دستورات زير را بنويسيد:


#include "SnakeGameEngine.h"
#include "snakecontainer.h"


پياده‌سازي دو تابع NewL و NewLC را به خودتان واگذار مي‌كنم. طبق توضيحاتي كه در بخش «سازنده دو فازي» دادم، بايد به راحتي بتوانيد اين دو تا را بنويسيد.
پياده‌سازي شما بايد شبيه زير باشد:


CSnakeGameEngine* CSnakeGameEngine::NewL(CsnakeContainer* aOwningControl)
..
CSnakeGameEngine* self = NewLC(aOwningControl);
CleanupStack::Pop(self);
return self;
}

CSnakeGameEngine* CSnakeGameEngine::NewLC(CsnakeContainer* aOwningControl)
..
CSnakeGameEngine* self = new (ELeave) CSnakeGameEngine(aOwningControl);
CleanupStack::PushL(self);
self->ConstructL();
return self;
}


Constructor و Destructor كلاس را هم مثل كد زير پياده‌سازي كنيد:


CSnakeGameEngine::CSnakeGameEngine(CsnakeContainer * aOwningControl)
:
iOwningControl(aOwningControl),
iGameState(ENotStarted),
iMaxSnakeSize(3)
..
}

CSnakeGameEngine::~CSnakeGameEngine()
..
DELETEANDNULL(iSnake);
StopTimer();

DELETEANDNULL(iBackBufferBmp);
DELETEANDNULL(iGc);
DELETEANDNULL(iBackBufferBmpDrawingDevice);
}


ماكروي DELETEANDNULL هم كه در فايل .h تعريف شده است.

mousa_mk
04-08-2007, 11:33
ما بايد در برنامه‌مان يك آبجكت از كلاس Engine داشته باشيم. براي اين منظور در فايل snakecontainer.h تعريف زير را در بخش publicها بنويسيد:


CSnakeGameEngine* iGameEngine;


يكي از قراردادهاي كدنويسي سيمبين (Coding Convention) اين است كه تمام متغيرهاي عضو بايد داراي يك i در اولشان باشند و همچنين تمام پارامترهاي يك تابع هم بايد داراي a باشند. براي متغيرهاي محلي (local) قانوني وجود ندارد.

حال بايد اين اشاره‌گر را در جايي new كنيم. همانطور كه در بخش «سازنده دو فازي» توضيح دادم، اين كار بايد در تابع ConstructL كلاس انجام شود.
در فايل snakecontainer.cpp به تابع ConstructL برويد و كد زير را در انتهاي آن بنويسيد.


// Create Game Engine:
iGameEngine = CSnakeGameEngine::NewL(this);
iGameEngine->StartNewGame();


در اينجا آبجكتي از Engineمان ايجاد مي‌كنيم و متد StartNewGame آن را صدا مي‌زنيم تا يك بازي جديد شروع شود. البته مي‌توانستيم كلاسمان را طور ديگري نيز طراحي كنيم تا موقع ايجاد شدن آبجكتي از آن، به طور اتوماتيك اين تابع صدا زده شود، كه البته بهتر نيز مي‌بود، ولي خب من در اينجا اينگونه نوشته‌ام.

الان هر موقع كه بازي ما در موبايل اجرا شود، آبجكتي از Engine نيز ايجاد مي‌شود و كار خود را شروع مي‌كند.
قبل از اين كه به ادامه طراحي Engine بپردازيم، ما به يك مار هم احتياج داريم. يك مار كه بايد در صفحه به دستور كاربر حركت كند و مربع‌ها را بخورد. از آنجايي كه بر اساس برنامه‌نويسي شي‌گرا كد مي‌زنيم، بايد يك كلاس براي آن ايجاد كنيم.

يك فايل جديد با نام Worm و با نوع .h ايجاد كنيد. (طبق روش قبلي) دقت داشته باشيد كه اين فايل بايد در پوشه inc ذخيره شود. (البته به خاطر اينكه از قبل فايلي با نام Snake.h موجود بود، اسم ديگري انتخاب كرديم وگرنه نام Snake مناسب‌تر بود.) كد زير را كه مربوط به تعريف كلاس CSnake مي‌باشد، در آن كپي كنيد:


#ifndef __WORM_H__
#define __WORM_H__

#include <e32base.h>
#include <w32std.h>
#include <gdi.h>
#include "TQueue.h"



class CMKSnakeGameEngine;


class CSnake: public CBase
..
public:
//--------------------------------------------------------------------------
// These two functions, perform the first phase of construction
//--------------------------------------------------------------------------
static CSnake* NewL(CFbsBitGc* aGc, CMKSnakeGameEngine* aOwningEngine);
static CSnake* NewLC(CFbsBitGc* aGc, CMKSnakeGameEngine* aOwningEngine);

//--------------------------------------------------------------------------
// Destructor
//--------------------------------------------------------------------------
~CSnake();

//--------------------------------------------------------------------------
// This function, initializes the snake
//--------------------------------------------------------------------------
void InitSnake();

//--------------------------------------------------------------------------
// This function draws the snake in the screen
//--------------------------------------------------------------------------
void Draw();

//--------------------------------------------------------------------------
// This function moves the snake one cell and if it hits wall or itself,
// returns EFalse, otherwise ETrue
//--------------------------------------------------------------------------
TBool MoveOneCell();

private:

//--------------------------------------------------------------------------
// Private constructor for class
//--------------------------------------------------------------------------
CSnake(CFbsBitGc* aGc, CMKSnakeGameEngine* aOwningEngine);

//--------------------------------------------------------------------------
// This function performs the second phase of construction
//--------------------------------------------------------------------------
void ConstructL();

public:
//--------------------------------------------------------------------------
// An enumeration for snake's state
//--------------------------------------------------------------------------
enum TSnakeState
..
EStarting,
EMoving,
EHitWallOrSelf
};

//--------------------------------------------------------------------------
// An enumeration for snake's direction
//--------------------------------------------------------------------------
enum TSnakeDirection
..
ELeft,
ERight,
EUp,
EDown
};

//--------------------------------------------------------------------------
// State of the snake
//--------------------------------------------------------------------------
TSnakeState iState;

//--------------------------------------------------------------------------
// Direction of the snake
//--------------------------------------------------------------------------
TSnakeDirection iDirection;

//--------------------------------------------------------------------------
// A Queue for keeping snake
//--------------------------------------------------------------------------
TQueue* iQueue;

//--------------------------------------------------------------------------
// A GC (Graphics Context) for drawing snake. (Initializes in constructor)
//--------------------------------------------------------------------------
CFbsBitGc* iGc;

//--------------------------------------------------------------------------
// A pointer to the 'Engine' class that owns this snake
//--------------------------------------------------------------------------
CSnakeGameEngine* iOwningEngine;

//--------------------------------------------------------------------------
// These four functions perform the moving of the snake
//--------------------------------------------------------------------------
void MoveUp();
void MoveDown();
void MoveLeft();
void MoveRight();

//--------------------------------------------------------------------------
// Color of the snake. (In our game, this is always fixed, But you can
// give it from user in a 'settings' form...)
//--------------------------------------------------------------------------
TRgb iColor;

};

#endif


توضيحات لازم را داخل كد داده‌ام. حال براي پياده‌سازي Snake فايلي جديد با نام Worm.cpp ايجاد كنيد و سپس طبق روش قبلي آن را به پروژه اضافه كنيد.

mousa_mk
04-08-2007, 11:35
اگر دقت كرده باشيد، در كلاس CSnake عضوي از نوع TQueue داريم. اين نوع از قبل تعريف نشده است و ما بايد خودمان آن را تعريف كنيم. (البته من در بين كلاس‌هاي سيمبين دنبال كلاسي براي صف گشتم ولي چيزي پيدا نكردم، به همين خاطر مجبور شدم خودم آن را پياده‌سازي كنيم.)
قبل از اين كه به پياده‌سازي كلاس CSnake كه تعريف كرديم، بپردازيم، بايد اين كلاس را بنويسيم. براي خود كلاس هم از كلاسي با نام TNode استفاده كرده‌ام كه بايد آن را هم پياده‌سازي كنيم.

پس يك فايل با نام Node.h ايجاد كنيد و كد زيرا در آن بنويسيد:


#ifndef __NODE_H__
#define __NODE_H__

#include <e32std.h>

class TNode
..
public:
TNode();
TNode(TPoint aPoint);

public:
TNode* next;
TNode* prev;

TPoint iPoint;
};

#endif


فايل Node.cpp را هم ايجاد كنيد و كد زير را در آن بنويسيد:


#include "TNode.h"
//#include <e32std.h>

TNode::TNode()
..
iPoint.SetXY(-1, -1);
next = NULL;
prev = NULL;
}

TNode::TNode(TPoint aPoint)
..
iPoint = aPoint;
next = NULL;
prev = NULL;
}


اين كلاس يك كلاس خيلي ساده است كه فقط يك گره را پياده‌سازي مي‌كند. (گره‌ها در ساختارهاي صف، ليست پيوندي و ... استفاده مي‌شوند.)

حال دو فايل با نام‌هاي Queue.h و Queue.cpp ايجاد كرده و كدهاي زيرا به ترتيب در آنها بنويسيد:


#ifndef __QUEUE_H__
#define __QUEUE_H__

#include "Node.h"

class TQueue
..
public:
// -----------------------------------------------------------------------
// Constructor and Destructor
// -----------------------------------------------------------------------
TQueue();
~TQueue();

// -----------------------------------------------------------------------
// For saving a point in the queue
// -----------------------------------------------------------------------
void enQueue(TPoint aPoint);

// -----------------------------------------------------------------------
// Getting the first point from queue and deleting it from queue
// -----------------------------------------------------------------------
TPoint deQueue();

// -----------------------------------------------------------------------
// Getting the last point (but not deleting it)
// -----------------------------------------------------------------------
TPoint GetLastPoint();

// -----------------------------------------------------------------------
// Getting the one to last point (but not deleting it)
// -----------------------------------------------------------------------
TPoint GetOneToLastPoint();

// -----------------------------------------------------------------------
// Checks if the queue has the point or not
// -----------------------------------------------------------------------
TBool hasPoint(TPoint aPoint);

// -----------------------------------------------------------------------
// Deletes all nodes from the queue
// -----------------------------------------------------------------------
void ResetQueue();

// -----------------------------------------------------------------------
// Returns the size of queue (The number of nodes in it.)
// -----------------------------------------------------------------------
TInt Size();

private:
// -----------------------------------------------------------------------
// The first node in queue
// -----------------------------------------------------------------------
TNode* start;
};

#endif




#include "Queue.h"


TQueue::TQueue()
..
start = NULL;
}


TQueue::~TQueue()
..
if(!start)
;
else if(!start->next)
delete start;
else
..
TNode* i = start;
TNode* pre = NULL;
while(i)
..
pre = i;
i = i->next;
delete pre;
}
}
}

void TQueue::enQueue(TPoint aPoint)
..
if(!start)
..
start = new TNode(TPoint(aPoint.iX, aPoint.iY));
}
else
..
for(TNode* i = start; i->next; i = i->next)
;
i->next = new TNode(TPoint(aPoint.iX, aPoint.iY));
i->next->prev = i;
}
}

TPoint TQueue::deQueue()
..
if(!start)
return TPoint(-1, -1);
else if(!start->next)
..
TPoint p = TPoint(start->iPoint.iX, start->iPoint.iY);
delete start;
start = NULL;

return p;
}
else
..
TPoint p = TPoint(start->iPoint.iX, start->iPoint.iY);
start = start->next;
delete start->prev;
start->prev = NULL;

return p;
}
}

TBool TQueue::hasPoint(TPoint aPoint)
..
if(!start)
return EFalse;

for(TNode* i=start; i!=NULL; i = i->next)
if(i->iPoint == aPoint)
break;

return (i != NULL);
}

void TQueue::ResetQueue()
..
while(deQueue() != TPoint(-1, -1))
;
}

TPoint TQueue::GetLastPoint()
..
if(!start)
return TPoint(-1, -1);

for(TNode* i=start; i->next; i = i->next)
;

return TPoint(i->iPoint.iX, i->iPoint.iY);
}

TPoint TQueue::GetOneToLastPoint()
..
if(!start || !start->next)
return TPoint(-1, -1);

for(TNode* i=start; i->next->next; i = i->next)
;

return TPoint(i->iPoint.iX, i->iPoint.iY);
}

TInt TQueue::Size()
..
if(!start)
return 0;

TInt count=0;
for(TNode* i=start; i; i = i->next)
count++;

return count;
}


كد بالا هم مربوط به پياده‌سازي كلاس صف بود كه به احتمال زياد بلديد و نيازي به توضيح نيست. البته من خودم غير از متدهاي استاندارد، تعدادي متد كمكي ديگر نيز اضافه كردم.

Amin_Eman
04-08-2007, 15:58
با تشکر فراوان از آقا موسی عزیز تاپیک مهم شد!

menamechanic
05-08-2007, 01:08
man ke kheili hal kardam

dezbluestar
05-08-2007, 01:53
موسی جون واقعا دستت درد نکنه
نمی دونی به کجا ها خودمو زدم ( هرچی درو دیوار تو اینترنت می بینی )

به هر حال ممنون

dezbluestar
05-08-2007, 01:56
اقا موسی عزیز
اگه تمرین بدی ممنون می شم که خودمو نو محک بزنیم

mousa_mk
07-08-2007, 14:05
در دو كلاسي كه در بالا ساختيم، مي‌بينيد كه اول اسمشان، T گذاشتيم. همچنين در كلاس‌هاي قبلي هم مي‌بينيد كه اكثرشان داراي حرف C در ابتداي اسمشان هستند. ولي اين حروف چه معنايي مي‌دهند؟

يكي از قراردادهاي كدنويسي (Coding Convention) در سيمبين اين است كه اول نام كلاس‌ها، حتماً بايد يكي از حروف T، C، M و يا R باشد. توضيح اين كلاس‌ها به شرح زير هستند:

كلاس‌هاي T: اين گونه كلاس‌ها، كلاس‌هاي ساده‌اي هستند كه نيازي به Destructor ندارند. typeهايي مانند TInt، TDouble، به خاطر اين كه نيازي به Destructor ندارند، از نوع T هستند. ممكن است كلاس‌هاي پيچيده‌تري هم از نوع T باشند، اين كلاس‌ها هم كلاس‌هايي هستند كه اعضايشان كلاس‌هاي T هست و لاغير. مثلاً TPoint كه داراي دو تا متغير TInt است و يا همچنين است TSize. enumها هم معمولاً از نوع T هستند. اين گونه كلاس‌ها را هم مي‌توان در Stack و هم مي‌توان در Heap ايجاد كرد.
كلاس‌هاي C: تمام كلاس‌هاي معمولي از اين نوع هستند. تمام اين كلاس‌ها در نهايت از كلاس CBase مشتق مي‌شوند كه اين كلاس داراي يك Destructor مجازي و چندتا overload از عملگر new است. معمولاً اين كلاس‌ها با روش «سازنده دو فازي» پياده‌سازي مي‌شوند. به همين دليل هم هميشه بايد در heap ايجاد شوند.
كلاس‌هاي M: اين كلاس‌ها فقط داراي تعريف متد هستند و چيز ديگري ندارند. معادل با مفهوم Interface در جاوا.
كلاس‌هاي R: از اين كلاس‌ها براي متصل شدن به سرورها و استفاده كردن از خدمات آنها استفاده مي‌شود. اين گونه كلاس‌ها هميشه داراي متدهايي مثل Open يا Connect و همچنين Close هستند. معروفترين مثال از اين دسته، كلاس Rfile مي‌باشد كه براي اتصال به File Server استفاده مي‌شود.

حال مي‌توانيد علت اين كه بعضي از كلاس‌هايمان را با C و بعضي ديگر را با T نامگذاري، كرديم را بفهميد.


قبل از اين كه به ادامه كارمان بپردازيم، تعدادي اشکالات موجود در پست‌هاي قبلي را تصحیح مي‌كنيم.
در فايل snakecontainer.h كه يك اشاره‌گر از CsnakeGameEngine گرفتيم، بايد فايل SnakeGameEngine.h را هم include كنيد.
در فايل SnakeGameEngine، فايل Snake.h را include كرده‌ايم. شما بايد آن را به Worm.h تغيير دهيد.
در فايل Worm.h، فايل TQueue.h را به Queue.h تغيير دهيد.
دوباره در فايل Worm.h يك forward declaration از كلاسي با نام CMKSnakeGameEngine انجام داده‌ايم؛ آن را به CSnakeGameEngine تغيير نام دهيد.
در همين فايل، در سطرهاي بعدي هم تعدادي اين نام به كار برده شده است، آن را طبق بالا عوض كنيد. (در سه تابع NewL، NewLC و Constructor)
در فايل SnakeGameEngine.h هم تغيير بالا را انجام دهيد.
دو همين فايل، چند بار هم از نام CMKSnakeContainer استفاده شده است. آنها را به CsnakeContainer تغيير نام دهيد. (اين دفعه به كوچك بودن حرف s دقت داشته باشيد!)
با عرض پوزش از تمام كساني كه اين آموزش را دنبال مي‌كنند. متأسفانه به علت اينكه نامگذاري‌هاي من در سورس اصلي با اين سورس جديدي كه دوباره براي آموزش مي‌نويسم، يكي نيستند، و كمي هم در اثر بي‌دقتي بنده، اين چنين اشكالاتي پيش آمده است.



قبل از پياده‌سازي كلاس CSnake كمي درباره معماري برنامه‌مان صحبت مي‌كنيم. طرح اصلي صفحه نمايش به صورت زير خواهد بود:

http://mousa.persiangig.com/image/others/MobilestanTutorial/pic08.JPG

در كادر بالايي اطلاعات لازم را خواهيم نوشت. (امتياز فعلي و بيشترين امتيازي كه در فايل ذخيره شده است.) كادر پاييني هم ميدان بازي و جايي است كه مار در آنجا حركت خواهد كرد.
فاصله ديواره اضلاع كادرها تا لبه تصوير (در هر چهار طرف) 3 پيكسل مي‌باشد. ضخامت تمام اضلاع هم 1 پيكسل مي‌باشد. بقيه فواصل در تصوير زير نشان داده شده است:

http://mousa.persiangig.com/image/others/MobilestanTutorial/pic09.JPG

(دقت داشته باشيد كه كل صفحه موبايل‌هاي سري 60، داراي رزولوشن 176*208 مي‌باشند.)
همانطور كه مي‌بينيد، ميدان بازي داراي ابعاد 168*168 مي‌باشد. ما اين مربع را به به مربع‌هاي 8*8 تقسيم مي‌كنيم. حال ميدان بازي ما داراي ابعاد 21*21 مي‌باشد. براي آدرس‌دهي به يكي از مربع‌هاي اين ميدان از كلاس TPoint استفاده خواهيم كرد. TPointها براي مشخص كردن يك پيكسل به كار مي‌روند، ولي ما در اينجا براي اشاره كردن به مربع‌ها هم از آنها استفاده خواهيم كرد.


حال، طبق اين اطلاعات به پياده‌سازي كلاس CSnake مي‌پردازيم. ابتدا includeهاي زير را بنويسيد:


#include <e32math.h>
#include "Worm.h"
#include "SnakeGameEngine.h"
#include "snakecontainer.h"


حالا دو تابع NewL و NewLC را مي‌نويسيم: (دقيقاً طبق روش قبلي)


CSnake* CSnake::NewL(CFbsBitGc* aGc, CSnakeGameEngine* aOwningEngine)
..
CSnake* self = NewLC(aGc, aOwningEngine);
CleanupStack::Pop(self);
return self;
}

CSnake* CSnake::NewLC(CFbsBitGc* aGc, CSnakeGameEngine* aOwningEngine)
..
CSnake* self = new (ELeave) CSnake(aGc, aOwningEngine);
CleanupStack::PushL(self);
self->ConstructL();
return self;
}


Constructor و Destructor كلاس را به صورت زير پياده‌سازي كنيد:


CSnake::CSnake(CFbsBitGc* aGc, CSnakeGameEngine* aOwningEngine)
:
iState(EStarting),
iColor(TRgb(0, 255, 0)),
iDirection(ERight),
iGc(aGc),
iOwningEngine(aOwningEngine)
..
}

CSnake::~CSnake()
..
if(iQueue)
delete iQueue;
}

همانطور كه مي‌بينيد در Constructor كلاس، فقط به مقداردهي اوليه متغيرهاي عضو كلاس كه به new كردن نيازي ندارند، پرداخته‌ايم. در Destructor هم iQueue را در صورت وجود پاك كرده‌ايم.

تابع ConstructL را هم به صورت زير بنويسيد:


void CSnake::ConstructL()
..
iQueue = new TQueue();
InitSnake();
}


تابع InitSnake كه در اينجا استفاده شده است، صف كلاس را كلاً پاك مي‌كند و دوباره سه خانه اول را در صف اضافه مي‌كند. (مار ما در شروع داراي اندازه 3 است و در گوشه پايين چپ قرار دارد. بنابراين، بايد سه خانه با مختصات (0,20)، (1,20) و (2,20) را اضافه كنيم.) پس تابع InitSnake بايد به صورت زير پياده‌سازي شود:


void CSnake::InitSnake()
..
iQueue->ResetQueue();

iQueue->enQueue(TPoint(0, 20));
iQueue->enQueue(TPoint(1, 20));
iQueue->enQueue(TPoint(2, 20));
}

mousa_mk
07-08-2007, 14:12
اكنون به ادامه پياده‌سازي كلاس Snake مي‌پردازيم.

تابع Move وظيفه‌اش، كشيدن مار در صفحه مي‌باشد.
در كلاس Snake ما يك اشاره‌گر با نام iGc داريم كه اين اشاره‌گر Graphic Contextاي كه در صفحه كشيده خواهد شد. يعني هر چيزي كه در اين GC بكشيم، در صفحه نمايش نشان داده خواهد شد. به عبارت ديگر اين اشاره‌گر، صفحه نقاشي ما مي‌باشد!

تابع Draw را به صورت زير بنويسيد:


void CSnake::Draw()
..
iGc->SetPenStyle(CGraphicsContext::ESolidPen);
iGc->SetPenColor(TRgb(255, 255, 255)); // White Color for Pen
iGc->SetBrushColor(TRgb(255, 255, 255)); // White Color for Brush
iGc->SetBrushStyle(CGraphicsContext::ESolidBrush);

switch(iState)
..
case EHitWallOrSelf:
break;


case EMoving:
case EStarting:
for(TInt i=0;i<21;i++)
for(TInt j=0;j<21;j++)
..
if(iQueue->hasPoint(TPoint(i, j)))
..
iGc->DrawRect(TRect(TPoint(4+8*i, 36+j*8), TPoint(4+8*i+8, 36+j*8+8)));
iGc->SetBrushColor(iColor);
iGc->DrawRect(TRect(TPoint(4+8*i+1, 36+j*8+1), TPoint(4+8*i+8-1, 36+j*8+8-1)));
}
}

iGc->SetBrushColor(KRgbBlue);
TPoint p = iQueue->GetLastPoint();
iGc->DrawRect(TRect(TPoint(4+8*p.iX+1, 36+p.iY*8+1), TPoint(4+8*p.iX+8-1, 36+p.iY*8+8-1)));
break;
}
}

تمام كارهايي كه انجام داده‌ايم، واضح هستند. براي شرح متدهاي iGc، مخصوصاً آنهايي كه در اينجا استفاده كرده‌ايم، مي‌توانيد به SDK Help مراجعه كنيد. (از طريق منوي Start – در Help به توضيحات كلاس CfbsBitGc مراجعه كنيد.)

توضيح مختصر: ابتدا مشخصاتي مانند رنگ و ... را تنظيم مي‌كنيم. سپس با توجه به وضعيت مار، اگر مار مرده باشد، (يعني به ديوار يا خودش برخورد كرده باشد) آن را نمي‌كشيم.
ولي اگر در شروع حركت و يا به حالت عادي يعني در حال حركت باشد، آن را مي‌كشيم.
كشيدن مار هم به اين صورت است كه تمام مربعات ميدان بازي را يكي‌يكي بررسي مي‌كنيم. اگر مربعي جزئي از مار باشد، با كشيدن دو تا مستطيل، آن را مي‌كشيم. ابتدا يك مستطيل به اندازه كامل مربع با رنگ سفيد مي‌كشيم و سپس يك مستطيل ديگر با يك پيكسل تورفتگي از هر طرف، با رنگ خود مار مي‌كشيم.
(شما مي‌توانيد اين كد را عوض كنيد و مار را هرگونه كه دوست داريد، بكشيد. مثلاً مي‌توانيد عكس‌هاي از قبل ذخيره شده را لود كنيد و يا اگر EGL يا همان OpenGL ES بلد باشيد، مي‌توانيد مار را به صورت 3D بكشيد.)

حالا مي‌خواهيم متد MoveOneCell را پياده‌سازي كنيم. همانطور كه از نامش معلوم است، كار اين تابع، حركت كردن يك واحد به جلو است. اگر خانه جلويي وجود نداشته باشد (يعني در حال برخورد به ديوار) و يا اگر خانه جلويي جزئي از خود مار باشد (يعني در حال برخورد به خودش) تابع مقدار EFalse برمي‌گرداند. ولي در صورتي كه شرايط عادي باشد و مار حركت خودش را انجام دهد، مقدار ETrue برمي‌گرداند.
ضمناً اگر خانه جلويي مار، همان مربعي باشد كه مار بايد بخورد، ابتدا يك واحد به طول مار اضافه مي‌كنيم و سپس Engine را از اين رويداد باخبر مي‌سازيم تا كارهاي لازم (مثلاً توليد خانه تصادفي ديگر و ...) را انجام دهد.

البته اضافه كردن يك واحد به طول مار، اينگونه است كه اگر مار مربع مذكور نرسد، يك خانه از انتهاي مار حذف مي‌كنيم (از صف deQueue مي‌كنيم) و خانه جلوي مار را اضافه مي‌كنيم. (به صف enQueue مي‌كنيم)
ولي اگر مار به مربع برخورد كند، ديگر عمل deQueue را انجام نمي‌دهيم. به اين ترتيب، به طور اتوماتيك يك واحد به طول مار اضافه مي‌شود.

و حالا كد:


TBool CSnake::MoveOneCell()
..
TBool returnVal = ETrue;

TPoint p = iQueue->GetLastPoint();

switch(iDirection)
..
case ERight:
if(p.iX == 20 || iQueue->hasPoint(TPoint(p.iX+1, p.iY)))
..
iState = EHitWallOrSelf;
returnVal = EFalse;
}
else
..
if(iOwningEngine->iCurRandPoint == TPoint(p.iX+1, p.iY))
..
iQueue->enQueue(TPoint(p.iX+1, p.iY));
iOwningEngine->GotTheRandPoint();
}

else
..
iQueue->enQueue(TPoint(p.iX+1, p.iY));
iQueue->deQueue();
}
}
break;

case ELeft:

if(p.iX == 0 || iQueue->hasPoint(TPoint(p.iX-1, p.iY)))
..
iState = EHitWallOrSelf;
returnVal = EFalse;
}
else
..
if(iOwningEngine->iCurRandPoint == TPoint(p.iX-1, p.iY))
..
iQueue->enQueue(TPoint(p.iX-1, p.iY));
iOwningEngine->GotTheRandPoint();
}

else
..
iQueue->enQueue(TPoint(p.iX-1, p.iY));
iQueue->deQueue();
}
}
break;

case EUp:

if(p.iY == 0 || iQueue->hasPoint(TPoint(p.iX, p.iY-1)))
..
iState = EHitWallOrSelf;
returnVal = EFalse;
}
else
..
if(iOwningEngine->iCurRandPoint == TPoint(p.iX, p.iY-1))
..
iQueue->enQueue(TPoint(p.iX, p.iY-1));
iOwningEngine->GotTheRandPoint();
}

else
..
iQueue->enQueue(TPoint(p.iX, p.iY-1));
iQueue->deQueue();
}
}
break;

case EDown:

if(p.iY == 20 || iQueue->hasPoint(TPoint(p.iX, p.iY+1)))
..
iState = EHitWallOrSelf;
returnVal = EFalse;
}
else
..
if(iOwningEngine->iCurRandPoint == TPoint(p.iX, p.iY+1))
..
iQueue->enQueue(TPoint(p.iX, p.iY+1));
iOwningEngine->GotTheRandPoint();
}

else
..
iQueue->enQueue(TPoint(p.iX, p.iY+1));
iQueue->deQueue();
}
}
break;

}

return returnVal;
}

توضيح كد: ابتدا با يك دستور switch جهت مار را مشخص مي‌كنيم. در هر جهت، ابتدا بررسي مي‌كنيم كه اگر مار در آخرين رديف ممكن قرار داشته باشد و يا اگر خانه جلويي جزئي از خود مار باشد، EFalse برمي‌گردانيم. سپس بررسي مي‌كنيم كه اگر خانه جلويي، مربعي باشد كه مار بايد بايد بخورد، آن را به مار اضافه مي‌كنيم و Engine را خبردار مي‌كنيم. و در غير اين صورت، خانه جلويي را به مار اضافه مي‌كنيم و آخرين خانه را حذف مي‌كنيم.

در كلاس Snake، چهارتا تابع ديگر هم وجود دارد كه كار تغيير جهت مار را برعهده دارند و موقع زده شدن كليدهاي جهت، صدا زده مي‌شوند.
اولين تابع، يعني MoveUp را به صورت زير مي‌نويسيم:


void CSnake::MoveUp()
..
if(EDown == iDirection)
return;

iDirection = EUp;
}

توضيح: ابتدا بررسي مي‌كنيم كه كاربر چرخش 180 درجه انجام ندهد. يعني مثلاً اگر در حال حركت رو به جلو است، نمي‌تواند يك دفعه به عقب برگردد. (البته شما مي‌توانيد خودتان اين حالت را هم ساپورت كنيد. مثلاً اگر اين چنين اتفاقي افتاد، مي‌تواند ما را سروته كنيد. ولي كدنويسيش با شما. البته كار زيادي ندارد!)
حال اگر چرخش 180 درجه نباشد، جهت را تغيير مي‌دهيم.

سه تابع ديگر را خودتان به صورت مشابه تابع بالا، بنويسيد.
توضيح اضافي: البته مي‌توانستيم به جاي چهار تابع، يك تابع Move بنويسيم كه جهت حركت را از طريق پارامتر بگيرد و كارهاي لازم را با توجه به پارامتر انجام دهد. كه البته به‌صرفه‌تر نيز مي‌باشد. ولي خب من اينجوري نوشته‌ام!! شما خود مي‌توانيد كد را به حالت ياد شده بنويسيد.

يك توضيح كدنويسي كوچك ديگر هم لازم است: همانطور كه مي‌بينيد من در بالا در داخل شرط if عبارت تساوي را برعكس نوشتم. اين كار را براي اين كردم تا اگر وقتي به جاي عملگر ==، عملگر = زدم (كه اشتباه بسيار رايجي است!) كامپايلر error بدهد. (البته اين نكته ربطي به برنامه‌نويسي موبايل ندارد.)

حالا كلاس CSnake ما كامل شده است و به راحتي مي‌توانيم از آن استفاده كنيم.

killer_pant
07-08-2007, 18:57
سلام آقا موسی
ممنون از آموزش زیبای شما
فقط یه خواهش دارم
اگه میشه بعد از اموزش snake...یه آموزشی قرار بدید که بتونیم یه متن یا عکسی رو تو یه برنامه بزاریم
به آموزش برنامه شخصا خیلی نیاز دارم تا بازی ..یکمم اگه میشه سطح رو بیارید پایین..
ممنون

mousa_mk
08-08-2007, 11:32
چشم. انشاءالله بعد از تموم کردنش، آموزش های کوتاهتر و کاربردی تری هم می نویسم.
در مورد سطح آموزش هم درست می فرمایین. متأسفانه سطحش برای تازه کارها یکمی سنگین شد.

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

بازم سعی خودم رو می کنم که کمی روانتر توضیح بدم.

mousa_mk
08-08-2007, 11:43
حالا كه مارمان را به طور كامل پياده‌سازي كرده‌ايم به نوشتن موتور بازي برمي‌گرديم.
اميدوارم كد موجود در فايل SnakeGameEngine.h را كه مربوط به تعريف كلاس Engine مي‌باشد، خوانده باشيد و معني تمام متدها و متغيرها را بدانيد. براي تمامشان توضيح مختصري به صورت كامنت نوشته‌ام. ضمناً نامشان هم نمايانگر همه چيز هست.

از كلاس Engine تا بحال فقط چهارتا تابع را نوشته‌ايم.
ابتدا بهتر است كد بخشي را بنويسيم كه مسئول ساختن اشاره‌گرهاي كلاس است. همانطور كه مي‌دانيد اين وظيفه برعهده تابع ConstructL مي‌باشد.
كدي كه من براي اين تابع نوشته‌ام، به صورت زير است. اين كد را به فايل SnakeGameEngine.cpp كپي كنيد:


void CSnakeGameEngine::ConstructL()
..
//1:
TRAPD(err, LoadGameProgressL());
if (err != KErrNone && err != KErrNotFound)
User::Leave(err);

//2:
TRect screenRect = CEikonEnv::Static()->EikAppUi()->ApplicationRect();
//3:
iBackBufferBmp = new (ELeave) CFbsBitmap;
//4:
User::LeaveIfError(iBackBufferBmp->Create(screenRect.Size(), EColor64K));
//5:
iBackBufferBmpDrawingDevice = CFbsBitmapDevice::NewL(iBackBufferBmp);
//6:
User::LeaveIfError(iBackBufferBmpDrawingDevice->CreateContext(iGc));

//7:
iTime.UniversalTime();
}

براي راحتتر توضيح دادن كد، سطرها را شماره‌گذاري كرده‌ام.
1) در اين بخش، تابع LoadGameProgressL را صدا مي‌زنيم. اين تابع، فايل ذخيره امتياز را در صورت وجود باز مي‌كند و امتياز را لود مي‌كند. پياده‌سازي اين تابع را بعداً خواهيم ديد. ماكروي TRAPD كه در اينجا استفاده كرده‌ايم، جزئي از سيستم Exception Handling سيمبين مي‌باشد. اين ماكرو، تابع پاس شده به خود را اجرا مي‌كند و در صورت leave شدن، كد خطا را در متغير اولي مي‌ريزد. ضمناً اين ماكرو، خودش متغير اولي را تعريف مي‌كند.
در خط بعدي بررسي مي‌كنيم كه اگر خطايي غير از موجود نبودن فايل رخ دهد، اجراي برنامه متوقف شود.

2) در اينجا، مستطيلي كه معرف صفحه نمايش است را از سيستم مي‌گيريم. دقت داشته باشيد كه دستور زير، آبجكت مربوط به AppUi برنامه ما را برمي‌گرداند:


CEikonEnv::Static()->EikAppUi()

تابع ApplicationRect هم، مستطيل برنامه را برمي‌گرداند. براي توضيح بيشتر در مورد دستور بالا مي‌توانيد به SDK Help مراجعه كنيد.
در برنامه‌هاي full screen، اين مستطيل حاوي تمامي صفحه نمايش است. يعني يك مستطيل با شروع از نقطه (0, 0) و با طول 176 * 208.

3) اين دستور كاملاً واضح است.

4) در اينجا، آبجكتي كه بالا new كرديم را Create مي‌كنيم. به عبارت ساده‌تر، آن را مي‌سازيم. براي ساختنش بايد سايز صفحه و همچنين تعداد رنگ‌هاي لازم را به آن بدهيم. در اينجا براي تعداد رنگ‌ها EColor64K را به آن داده‌ايم. يعني 64000 رنگ. بيشتر دستگاه‌هاي سري 60، اين مقدار را پشتيباني مي‌كنند. البته ما در بازي‌مان از تعداد بسيار محدودي رنگ استفاده خواهيم كرد و به اين مقدار نياز نداريم. مي‌توانيد مقدار كمتري را وارد كنيد.

5) در اينجا Device لازم براي كشيدن را ايجاد مي‌كنيم.

6) در اينجا هم Device را Create مي‌كنيم.

7) در اينجا متغير iTime را به زمان كنوني تنظيم مي‌كنيم. (اين متغير از نوع TTime مي‌باشد و براي كار كردن با زمان استفاده مي‌شود. توابع بسيار مهم و كاربردي دارد. البته ما در اينجا فقط براي توليد اعداد تصادفي غيرتكراري از آن استفاده خواهيم كرد. - توجه داشته باشيد كه با صدا زدن UniversalTime روي اين آبجكت، زمان كنوني در متغير ذخيره مي‌شود.)


در كلاس Container، متدي با نام Draw وجود دارد. كار اين متد كشيدن برنامه روي صفحه نمايش است. اين متد به طور اتوماتيك هر موقع كه لازم شود، برنامه روي صفحه كشيده شود، توسط framework صدا زده مي‌شود. مستطيلي هم كه به آن پاس مي‌شود، محوطه‌اي است كه فقط اجازه دارد در آن بكشد.
اين متد به طور پيشفرض توسط IDE نوشته شده است. اگر نگاهي به كد آماده بيندازيد، مي‌بينيد كه فقط يك مستطيل خالي در صفحه مي‌كشد. ما بايد اين متد را طوري تغيير دهيم كه آبجكت iGc ما را در صفحه بكشد. پس كد اين متد را به صورت زير تغيير دهيد:


void CsnakeContainer::Draw(const TRect& aRect) const
..
CWindowGc& gc = SystemGc();
iGameEngine->DrawScreen(aRect);

gc.BitBlt(TPoint(0,0), iGameEngine->iBackBufferBmp);
}

همانطور كه مي‌بينيد در اينجا ابتدا GC اصلي صفحه را مي‌گيريم. سپس تابع DrawScreen از موتور بازي را صدا مي‌زنيم. اين تابع تمام چيزهايي كه بايد در صفحه كشيده شوند، را در متغير iGc خودش مي‌كشد. و بالاخره iBackBufferBmp را در GC اصلي مي‌كشيم. (توجه داشته باشيد كه iGc و iBackBufferBmp باهم در ارتباطند و جدا از هم نيستند!)

به موتور بازي برمي‌گرديم. تابع DrawScreen را كه در بالا استفاده كرديم، به صورت زير بنويسيد:


void CSnakeGameEngine::DrawScreen(const TRect& aRect)
..
switch(iGameState)
..
case ENotStarted:
DrawInits(aRect);
DrawRects();
iSnake->Draw();
DrawText(RS_R_PRESSANYKEY,TRect(10, SCREEN_HEIGHT/2, SCREEN_WIDTH-10,
SCREEN_HEIGHT/2+20),TRgb(KRgbYellow), CEikonEnv::Static()->AnnotationFont(),
CGraphicsContext::ECenter, 0);
break;

case ENormal:
DrawInits(aRect);
DrawRects();
iSnake->Draw();
DrawRandPoint();
break;

case EEnded:
break;
}

DrawInformation();
}

توضيح: ابتدا حالت بازي را مشخص مي‌كنيم. (با دستور switch - براي هر حالت، چيزهاي متفاوتي با بكشيم.)
اگر بازي در حالت اوليه باشد، يعني همان صفحه‌اي كه بايد جمله «براي شروع دكمه‌اي را بزنيد» نوشته شود، كارهاي زير را انجام مي‌دهيم:
ابتدا تابع DrawInits را صدا مي‌زنيم. اين تابع صفحه را ريست مي‌كند. يعني يك مستطيل خالي روي چيزهاي قبلي رسم مي‌كند تا همه چيز پاك شود.
سپس تابع DrawRects را صدا مي‌زنيم. اين تابع دو تا مستطيلي كه در صفحه هستند را رسم مي‌كند. (يك مستطيل كوچك در بالا كه اطلاعات را داخل آن خواهيم نوشت و يك مستطيل بزرگ در پايين كه ميدان بازي در آن قرار خواهد گرفت.)
اكنون مار را در صفحه رسم مي‌كنيم. متد Draw مار را قبلاً نوشته‌ايم و نحوه كارش را مي‌دانيد.
بعد از دستور بالا هم جمله «دكمه‌اي را فشار دهيد» را رسم مي‌كنيم.

حالت عادي هم مشابه كد بالا است با اين تفاوت كه بعد از كشيدن مار، چيزي در صفحه نمي‌نويسيم و فقط خانه تصادفي (كه مار بايد آن را بخورد) را رسم مي‌كنيم.

در پايان هم متد DrawInformation را صدا مي‌زنيم كه اطلاعات بازي (امتيار فعلي و بالاترين امتياز) را در صفحه بكشد.


البته ما عبارتي كه در بالا نوشتيم را هنوز تعريف نكرده‌ايم. براي تعريف اين جمله و عبارت‌هاي ديگر، كارهاي زير را انجام دهيد:
در پنجره project در بخش سمت چپ، به قسمت Designs برويد و فايل snakecontainer.akn را اجرا كنيد.
چهار انتخاب در پايين وجود دارد. String table را برگزينيد.
حالا در اينجا با زدن دكمه add new string، عبارت‌هاي زير را وارد كنيد:

http://mousa.persiangig.com/image/others/MobilestanTutorial/pic10.JPG

در وارد كردن عبارت‌هاي بالا، به سه نكته زير توجه داشته باشيد:
1) قبل از نوشتن r_aboutmessage اول Max length را به مثلاً 80 تغيير دهيد، سپس متن را وارد كنيد.
2) بعد از وارد كردن size: و همچنين High Score: يك فاصله خالي هم وارد كنيد. چون بعداً اعداد را به اينها اضافه خواهيم كرد.
3) شما مي‌توانيد به جاي اينها متن دلخواه ديگري هم وارد كنيد. مثلاً مي‌توانيد فارسي بنويسيد ولي يك مشكل كوچك در مورد زبان‌هاي «راست به چپ» مثل فارسي وجود دارد و آن هم اين كه ترتيب حروف را برعكس روي صفحه چاپ مي‌كند. البته فقط متن‌هاي كه قرار است با تابع DrawText كشيده شوند. متن‌هاي ديگري كه در منوها و ... استفاده خواهيم كرد، مشكلي ندارند. پس اگر خواستيد فارسي بنويسيد، مقادير r_pressanykey و r_snakesize و r_highscore را به صورت معكوس وارد كنيد. مثلاً اگر مي‌خواهيد براي r_pressanykey از عبارت «كليدي را بزنيد» استفاده كنيد، آن را به صورت «دينزب ار يديلك» بنويسيد.

mousa_mk
08-08-2007, 11:47
اكنون توابعي كه در بالا در متد DrawScreen نوشتيم را پياده‌سازي مي‌كنيم.

اولين تابعي كه استفاده كرديم، DrawInits بود.
كد اين تابع به صورت زير است:


void CSnakeGameEngine::DrawInits(const TRect& aRect)
..
iGc->SetPenStyle(CGraphicsContext::ENullPen);
iGc->SetBrushColor(TRgb(0, 0, 0));
iGc->SetBrushStyle(CGraphicsContext::ESolidBrush);
iGc->DrawRect(aRect);
}

كد كاملاً واضح است و نيازي به توضيح ندارد.

تابع بعدي، DrawRects است:


void CSnakeGameEngine::DrawRects()
..
iGc->SetBrushStyle(CGraphicsContext::ESolidBrush);
iGc->SetBrushColor(TRgb(255, 0, 0));

TSize size = TSize(7, 7);
iGc->DrawRoundRect(TRect(TPoint(SCREEN_EDGE,SCREEN_EDGE ),
TPoint(SCREEN_WIDTH-SCREEN_EDGE, 27+SCREEN_EDGE)), size);
iGc->DrawRect(TRect(TPoint(SCREEN_EDGE, 2*SCREEN_EDGE+27),
TPoint(SCREEN_WIDTH-SCREEN_EDGE, SCREEN_HEIGHT-SCREEN_EDGE)));

iGc->SetBrushColor(TRgb(0, 0, 0));

iGc->DrawRoundRect(TRect(TPoint(SCREEN_EDGE+1,SCREEN_ED GE+1),
TPoint(SCREEN_WIDTH-SCREEN_EDGE-1, 27+SCREEN_EDGE-1)), size);
iGc->DrawRect(TRect(TPoint(SCREEN_EDGE+1, 2*SCREEN_EDGE+27+1),
TPoint(SCREEN_WIDTH-SCREEN_EDGE-1, SCREEN_HEIGHT-SCREEN_EDGE-1)));
}

توضيح: در اين تابع ما بايد دو مستطيل بكشيم. به علت اين كه مستطيلي كه framework برايمان مي‌كشد، هميشه توپر است، براي كشيدن مستطيل توخالي، ما دو بار مستطيل مي‌كشيم كه مستطيل دومي داراي يك پيكسل تورفتگي از هر طرف است. ضمناً مستطيل بالايي را (براي زيبايي!!) از نوع مستطيل با گوشه‌هاي گرد مي‌كشيم. توضيحات توابع استفاده شده را مي‌توانيد در SDK Help ببينيد.

تابع DrawRandPoint:


void CSnakeGameEngine::DrawRandPoint()
..
iGc->SetBrushStyle(CGraphicsContext::ESolidBrush);
iGc->SetBrushColor(KRgbYellow);
iGc->DrawRect(TRect(iCurRandPoint.iX*8+4, iCurRandPoint.iY*8+36,
iCurRandPoint.iX*8+4+8, iCurRandPoint.iY*8+36+8));
}

اين كد هم كاملاً واضح است. متغير iCurRandPoint را به مختصات صفحه تبديل مي‌كند و يك مربع 8*8 مي‌كشد. در اينجا من با رنگ زرد كشيده‌ام. مي‌توانيد به دلخواه رنگش را عوض كنيد.

و بالاخره تابع DrawInformation:


void CSnakeGameEngine::DrawInformation()
..
TInt size;
if(iSnake)
size = iSnake->iQueue->Size();
else
size = 3;

TBuf<10> buf1;
TBuf<20> buf2;
CEikonEnv::Static()->ReadResource(buf1, RS_R_SNAKESIZE);
CEikonEnv::Static()->ReadResource(buf2, RS_R_HIGHSCORE);
buf1.AppendNum(size);
buf2.AppendNum(iMaxSnakeSize);

iGc->SetBrushColor(KRgbBlack);
iGc->SetPenColor(KRgbYellow);
iGc->SetBrushStyle(CGraphicsContext::ESolidBrush);
iGc->SetPenStyle(CGraphicsContext::ESolidPen);
iGc->UseFont(CEikonEnv::Static()->AnnotationFont());

// Drawing 'Size: x'
iGc->DrawText(buf1, TRect(7, 7, 100, 27), 14, CGraphicsContext::ELeft , 2);
//Drawing 'High Score: x'
iGc->DrawText(buf2, TRect(60, 7, 169, 27), 14, CGraphicsContext::ERight, 2);
}

توضيح: (TBuf براي تعريف string در سيمبين به كار مي‌رود) ابتدا دو رشته تعريف مي‌كنيم. سپس اين دو تا را از از Resource لود مي‌كنيم. (توجه داشته باشيد كه C++BuilderX رشته‌هاي تعريفي ما را دوباره تعريف مي‌كند و اين دفعه يك RS_ به اولش اضافه مي‌كنيد و ضمناً با تماماً با حروف بزرگ مي‌نويسد.) بعد از اين كار، اعداد را به رشته‌ها، مي‌چسبانيم. و بعد از تنظيم كردن رنگ و ...، اين دو رشته را در صفحه رسم مي‌كنيم.

mousa_mk
08-08-2007, 11:50
اكنون مي‌خواهيم منوهاي بازي را ايجاد كنيم. این قسمت از آموزش کاملاً ساده و جذاب است!

دوباره مثل قبل، فايل snakecontainer.akn را اجرا كنيد. اين دفعه به نماي Menu برويد. همانطور كه مي‌بينيد، دو منو از اول برايمان ايجاد شده است. ما در اينجا سه تا لازم داريم. (Start، About و Exit)
از سمت چپ، روي Menu Item كليك كنيد و سپس روي صفحه شبيه‌ساز كليك كنيد تا يك منو هم اضافه شود.

اكنون روي اولين منو كليك كنيد تا مشخصاتش در سمت راست در پنجره properties نشان داده شود. نامش را از iMenuItem1 به iMenuItemStart تغيير دهيد. براي Text ID، مورد r_startnewgame را انتخاب كنيد.

براي منوي دوم هم، نام iMenuItemAbout را بگذاريد و عنوان r_about را انتخاب كنيد. اين دفعه گزينه EEikMenuItemSeparatorAfter را هم تيك بزنيد.

براي منوي سوم، نام iMenuItemExit و عنوان r_exit بگذاريد. اين دفعه براي Command مورد EAknCmdExit را انتخاب كنيد. با اين انتخاب اين مورد، ديگر لازم نيست كه براي اين منو، كدي بزنيم. اكنون منوي Exit كامل است و كار مي‌كند!

حالا منوهايمان كامل و قابل استفاده‌اند. اكنون مي‌خواهيم براي منوي start new game، كد بزنيم. اين منو را انتخاب كنيد. در سمت راست، كنار properties روي events كليك كنيد. حالا روي كادر خالي جلوي onViewCommand دابل‌كليك كنيد. به بخش كد راهنمايي مي‌شويد. در اينجا كد زير را وارد كنيد:


iGameEngine->StartNewGame();

با اين كد، بازي فعلي ناديده گرفته مي‌شود و يك بازي جديد شروع مي‌شود.

حالا دوباره به بخش طراحي منو برگرديد. اين دفعه about را انتخاب كنيد و در كادر onViewCommand دابل‌كليك كنيد. قبل از اين كه هرگونه كدي بنويسيد، به بخش طراحي منو برگرديد و به همان كادري برويد كه دابل‌كليك كرديد. به انتهاي نام تابع در اين كادر، يك حرف L اضافه كنيد. (به اين معني كه اين تابع يك leaving function است.) حال Enter را بزنيد تا دوباره به كد برگرديد. الان مي‌توانيد به صورت دستي كد بزنيم و يك Message Box را نشان دهيم ولي بهتر است اين كار را به عهده IDE بگذاريم و به صورت گرافيكي كارمان را انجام دهيم.

به فايل snakecontainer.akn برگرديد. اين دفعه به نماي Non-Visual برويد. در سمت راست روي CAknInformationNote كليك كنيد و سپس روي بخش خالي در وسط كليك كنيد تا يك پنجره پيغام جديد ايجاد شود. نام آن را به iAknInformationNoteAbout تغيير دهيد. در سمت راست روي properties كليك كنيد تا مشخصاتش نمايش داده شوند. براي Text ID مورد r_aboutmessage را انتخاب كنيد.

اكنون به كد مربوط به منوي about برگرديد. در اينجا كد زير را وارد كنيد:


ExecuteiAknInformationNoteAboutL();


حال در زمان اجراي برنامه، هر موقع كه كاربر منوي about را زد، يك پنجره پيغام به كاربر نمايش داده مي‌شود كه اطلاعاتي در مورد بازي و نويسنده آن، مي‌دهد.

(البته الان به علت اينكه تمام متدهاي كلاس Engine را ننوشته‌ايم، نمي‌توانيم بازي را تست كنيم، وگرنه مي‌توانستيد كار كردن منوي about پنجره پيغام را ببينيد.)

كدنويسي منوها هم تمام شد.

mousa_mk
12-08-2007, 13:33
اكنون مي‌خواهيم، key handling بازي را انجام دهيم.

به كلاس snakecontainer.cpp برويد. در كلاس container تابعي با نام HandleKeyEvents وجود دارد كه ما بايد كليدها را در اينجا مديريت كنيم.
اگر براي كليدي، كد نوشتيم، بايد اين تابع، true برگرداند تا سيستم عامل بفهمد كه ما اين كليد را مديريت كرده‌ايم و لازم نيست آن براي برنامه ديگري ارسال كند. براي كليدهايي كه ما در بازي نياز نداريم، false برمي‌گردانيم.

تابع را به صورت زير تغيير دهيد:


bool CsnakeContainer::HandleKeyEvents(const TKeyEvent& aKeyEvent, TEventCode aType)
..
bool handled = false;

if(iGameEngine->iGameState == CSnakeGameEngine::ENotStarted && aKeyEvent.iScanCode != EStdKeyDevice0)
..
iGameEngine->iGameState = CSnakeGameEngine::ENormal;
iGameEngine->StateChanged();
if(!iGameEngine->iPeriodicTimer) iGameEngine->StartTimerL();
handled = true;
return handled;
}


switch(aKeyEvent.iScanCode)
..
case EStdKeyUpArrow:
case '2':
if(iGameEngine->iGameState == CSnakeGameEngine::ENormal)
..
iGameEngine->iSnake->MoveUp();
handled = true;
}
break;

case EStdKeyDownArrow:
case '8':
if(iGameEngine->iGameState == CSnakeGameEngine::ENormal)
..
iGameEngine->iSnake->MoveDown();
handled = true;
}
break;

case EStdKeyLeftArrow:
case '4':
if(iGameEngine->iGameState == CSnakeGameEngine::ENormal)
..
iGameEngine->iSnake->MoveLeft();
handled = true;
}
break;

case EStdKeyRightArrow:
case '6':
if(iGameEngine->iGameState == CSnakeGameEngine::ENormal)
..
iGameEngine->iSnake->MoveRight();
handled = true;
}
break;

}

return handled;
}

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

در صورت وجود حالت بالا، true برمي‌گردانيم.
سپس كليد زده شده را بررسي مي‌كنيم. بازيكن مي‌تواند ما را با كليدهاي جهت و يا با كليدها 2، 4، 6 و 8 حركت دهد. كد مربوط به اين حالت‌ها را نوشته‌ايم.

اكنون كار كليدها هم تمام شده است. به پياده‌سازي موتور بازي برمي‌گرديم.
تابع DrawText را به صورت زير بنويسيد:


void CSnakeGameEngine::DrawText(TInt aResourceId, TRect aRect, TRgb aColor,
const CFont* aFont, CGraphicsContext::TTextAlign aHoriz, TInt aLeftMrg)
..
TBuf<30> buf;
CCoeEnv::Static()->ReadResource(buf, aResourceId);
iGc->SetBrushColor(KRgbBlack);
iGc->SetPenColor(aColor);
iGc->UseFont(aFont);
iGc->DrawText(buf, aRect, 10, aHoriz, aLeftMrg);
}

اين تابع، مشخصات متني كه بايد رسم شود را مي‌گيرد و آن را در GC مي‌نويسد.

اگر يادتان باشد براي منوي Start ما تابعي با نام StartNewGame را صدا زديم. اين تابع را بايد به صورت زير پياده‌سازي كنيد:


void CSnakeGameEngine::StartNewGame()
..
iGameState = ENotStarted;

if(iSnake)
DELETEANDNULL(iSnake);

if(iPeriodicTimer)
DELETEANDNULL(iPeriodicTimer);

iSnake = CSnake::NewL(iGc, this);

iSnake->iState = CSnake::EStarting;

iTime.UniversalTime();
CreateRandPoint();
iOwningControl->DrawDeferred();
}

توضيح: ابتدا حالت بازي را مشخص مي‌كنيم. سپس در صورت وجود مار و تايمر، آنها را حذف مي‌كنيم. در ادامه مار را دوباره ايجاد مي‌كنيم و وضعيتش را مشخص مي‌كنيم. iTime را به زمان كنوني تنظيم مي‌كنيم. سپس يك مربع تصادفي ايجاد مي‌كنيم و بالاخره به سيستم مي‌گوييم كه صفحه تغيير كرده است تا Draw را صدا بزند. (دقت داشته باشيد كه ما خودمان هرگز نبايد Draw را صدا بزنيم.)
نكته اضافه هم اينكه تايمر را در تابع key handling ايجاد مي‌كنيم. چون الان كه يك بازي جديد را شروع كرديم، مار بايد ثابت باشد و قبل از زده شدن كليدي، نبايد حركت كند. به همين خاطر الان در اينجا نيازي به تايمر نداريم.

تابع StartTimer را به صورت زير بنويسيد:


void CSnakeGameEngine::StartTimerL()
..
iPeriodicTimer = CPeriodic::NewL(CActive::EPriorityLow);
iPeriodicTimer->Start(KTickInterval, KTickInterval, TCallBack(TimerCallBack, this));
}

در اينجا ابتدا تايمر را ايجاد مي‌كنيم و سپس آن را شروع مي‌كنيم. اولين پارامتر تابع Start، مشخص كننده مدت زماني است كه بايد بگذرد تا تايمر اولين اجرايش را انجام دهد. پارامتر دوم هم دوره تايمر را مشخص مي‌كند. (ثابت KTickInterval را در فايل SnakeGameEngine.h معادل 0.15 ثانيه، تعريف كرده‌ايم.) پارامتر سوم، آدرس تابعي كه تايمر بايد صدا بزند را مي‌گيرد و همچنين آبجكتي كه بايد به آن تابع پاس شود. اين تابع بايد حتماً static باشد.

همين تابع TimerCallBack را كه در كلاس Engine قبلاً به حالت static تعريف كرده‌ايم، به صورت زير بنويسيد:


TInt CSnakeGameEngine::TimerCallBack(TAny* aObject)
..
((CSnakeGameEngine*)aObject)->DoCallBack();
return 1;
}

در اينجا يك تابع غيراستاتيك را روي آبجكتي كه گرفته‌ايم (اين آبجكت همان Engine است) صدا مي‌زنيم.

تابع DoCallBack را هم به صورت زير پياده‌سازي كنيد:


void CSnakeGameEngine::DoCallBack()
..
if(!iSnake->MoveOneCell())
SnakeDied();
iOwningControl->DrawDeferred();
}


همانطور كه مي‌بينيد كار اصلي تايمر در اين تابع انجام مي‌گيرد. در اينجا مار را حركت مي‌دهيم و در صورتي كه تابع MoveOneCell مقدار Efalse برگرداند، يعني اين كه به خودش يا به ديوار برخورد كرده است. در اين حالت تابع SnakeDied را صدا مي‌زنيم تا كارهاي لازم را انجام دهد. در دستور بعدي هم به سيستم خبر مي‌دهيم كه صفحه تغيير كرده است و بايد دوباره رسم كند.

تابع StopTimer هم بايد به صورت زير نوشته شود:


void CMKSnakeGameEngine::StopTimer()
..
if(iPeriodicTimer)
..
DELETEANDNULL(iPeriodicTimer);
}
}

killer_pant
22-08-2007, 00:06
موسی کجایی دلمون برات تنگ شده

mousa_mk
22-08-2007, 10:43
راستش احساس كردم كسي دنبال نمي‌كنه، يه خورده بيخيال شدم! مي‌بخشيد.
ادامه:


تابع SnakeDied را كه در پست قبلي استفاده كرديم، به صورت زير پياده‌سازي كنيد:


void CSnakeGameEngine::SnakeDied()
..
StopTimer();
iGameState = EEnded;
StateChanged();

TInt curSize = iSnake->iQueue->Size();
iMaxSnakeSize = (iMaxSnakeSize > curSize) ? iMaxSnakeSize : curSize;

SaveGameProgressL();
}

توضيح: ابتدا تايمر را متوقف مي‌كنيم. سپس حالت بازي را تنظيم مي‌كنيم. بعد خبر مي‌دهيم كه حالت بازي عوض شده است تا كارهاي لازم انجام شود.
در ادامه بررسي مي‌كنيم كه آيا امتياز اين بازي از بيشترين امتياز قبلي بيشتر است يا نه!
بعد از آن هم بيشترين امتياز را ذخيره مي‌كنيم.

تابع StateChanged را هم به صورت زير نوشته‌ام:


void CSnakeGameEngine::StateChanged()
..
switch(iGameState)
..
case ENotStarted:
break;

case ENormal:
//...
break;

case EEnded:
iSnake->Draw();
break;
}
}

البته همانطور كه مي‌بينيد اين تابع تقريباً هيچ كاري انجام نمي‌دهد. من قصد بهتر كردن بازي را داشتم و مي‌خواستم مقداري امكانات ديگر اضافه كنم كه هنوز انجام نداده‌ام. اين تابع را هم براي همان نوشته بودم. به همين خاطر، تابع الان كار زيادي انجام نمي‌دهد.

هنوز تعدادي تابع باقي مانده است كه ننوشته‌ايم.
تابع CreateRandPoint همانطور كه از نامش معلوم است، يك نقطه تصادفي انتخاب مي‌كند. (همان مربعي كه مار بايد بخورد!) اين تابع را به صورت زير بنويسيد:


void CSnakeGameEngine::CreateRandPoint()
..
TInt randX, randY;

do
..
iTime.UniversalTime();
TInt64 seed = iTime.Int64();
randX = Math::Rand(seed) % 21;
randY = Math::Rand(seed) % 21;
}while(iSnake->iQueue->hasPoint(TPoint(randX, randY)));

iCurRandPoint.SetXY(randX, randY);
}

كد اين تابع هم تقريباً واضح است. حلقه do while را هم براي اين گذاشته‌ام كه اگر يه وقت نقطه انتخاب روي مار باشد، دوباره انتخاب انجام دهد و نقطه ديگري انتخاب كند. از تابع Math::Rand هم براي ايجاد عدد تصادفي انتخاب كرده‌ايم. اين تابع به عنوان هسته، يك عدد از نوع Int64 مي‌گيرد. ما اين عدد را از iTime كه زمان فعلي را دارد، مي‌گيريم.

در كلاس Engine تابعي با نام GotTheRandPoint وجود دارد. اين تابع را مار، موقعي كه مربع را خورد، صدا مي‌زند. كد اين تابع بايد به صورت زير باشد:


void CSnakeGameEngine::GotTheRandPoint()
..
CreateRandPoint();
DrawRandPoint();

//...:
TInt size = iSnake->iQueue->Size();
iMaxSnakeSize = (iMaxSnakeSize > size) ? iMaxSnakeSize : size;
}

همانطور كه مي‌بينيد در اينجا، ابتدا يك نقطه تصادفي جديد انتخاب مي‌كنيم، سپس آن را در صفحه مي‌كشيم و ادامه اگر اندازه فعلي مار، از بيشترين اندازه موجود بيشتر شده باشد، بيشترين اندازه را با اندازه فعلي جايگزين مي‌كنيم.

الان فقط دو تا تابع مربوط به ذخيره بيشترين امتياز و لود كردن آن باقيمانده است. اين دو تا تابع را بدون توضيح مي‌نويسم. چون به كمي اطلاعات قبلي نياز است و اينجا مجال توضيح آنها وجود ندارد. البته زياد هم سخت نيستند. خودتان تقريباً مي‌توانيد كارشان را بفهميد.
تابع SaveGameProgressL را به صورت زير بنويسيد:


void CSnakeGameEngine::SaveGameProgressL()
..
TFileName fullName(KMKSnakeDataFile);
CompleteWithAppPath(fullName);

#ifdef __WINS__
fullName[0] = 'C';
#endif


CFileStore* store = CDirectFileStore::ReplaceLC(
CEikonEnv::Static()->FsSession(), fullName,
EFileWrite);
store->SetTypeL(KDirectFileStoreLayoutUid);

RStoreWriteStream stream;
TStreamId id = stream.CreateLC(*store);


stream.WriteInt32L(GAME_VERSION_NUMBER); //Saving game version
stream.WriteInt32L(iMaxSnakeSize); //Saving max score

stream.CommitL();
CleanupStack::PopAndDestroy();

store->SetRootL(id);
store->CommitL();
CleanupStack::PopAndDestroy();
}


تابع LoadGameProgressL را هم به صورت زير بنويسيد:


void CSnakeGameEngine::LoadGameProgressL()
..
TFileName fullName(KMKSnakeDataFile);
CompleteWithAppPath(fullName);

#ifdef __WINS__
fullName[0] = 'C';
#endif


CFileStore* store = CDirectFileStore::OpenLC(
CEikonEnv::Static()->FsSession(), fullName,
EFileRead);

RStoreReadStream stream;
stream.OpenLC(*store, store->Root());


TInt versionNumber = stream.ReadInt32L(); //Getting file version
if(versionNumber != GAME_VERSION_NUMBER) //Checking file version
User::Leave(KErrNotFound);
iMaxSnakeSize = stream.ReadInt32L();


CleanupStack::PopAndDestroy(2);
}


كد بازي تمام شد. در پست بعد نحوه نحوه ايجاد آيكن براي بازي و ... را توضيح خواهم داد.

Vahid_ath
25-08-2007, 00:06
مرسی موسی جان از بابت زحمتهایی که میکشی والا ما مدیریم نباید پست تشکر بزنیم ولی خداییش حیفم اومد به یه دکمه بسنده کنم

ممنون و موفق باشید

Iman7228
25-08-2007, 00:53
سلام آقا موسی و بقیه دوستان
من تصمیم گرفتم تا برنامه نویسی موبایل رو یاد بگیرم.
ولی مشکل بزرگی که دارم اینه که از Dial up استفاده میکنم و امکان دانلود نرم افزار های با حجم بالا برام مقدور نیست.
آقا موسی تو وبلاگتون هم نوشتم که پک برنامه نویسی موبایل رو ازتون می خرم.
نمیشه یه شماره حساب بدین تا من پول رو به حسابتون واریز کنم بعد سی دی رو برام بفرستین؟
قیمتش رو با هم به توافق می رسیم
لطفا ایمیل بزنید :
Iman7228@Gmail.com
منتظر جوابتونم

Behrooz-GSM
26-08-2007, 13:23
موسی جان
کارت واقعا عالی هست ادامه بده