۶ پروژه چالشبرانگیز برای برنامهنویسها
آستین هنلی، استاد دانشگاه تنسی -- کدام برنامهنویسی دوست ندارد یک پروژه جانبی برای خودش شروع کند؟ چه دانشجو باشید و چه باسابقه و مشغولِ کار، میدانید که پروژههای فرعی نه تنها رزومه آدم را چاق و چله میکنند، که یکی از بهترین راهها برای یادگیری هستند. اما چیزی که خیلیها نمیدانند، این است که دقیقا چه نوع پروژهای؟
این پست، مجموعهای از ایدهها در جواب به این سوال است. من از تکتک این پروژهها کلی چیز یاد گرفتهام؛ اما جذابترین ویژگی آنها این است که آدم میتواند چندین بار آنها را بکوبد و دوباره از اول بسازد. در این بین، هربار هم چیزهای کاملا جدیدی را تجربه کند. هنوز هم که هنوز است، هر وقت میخواهم فریمورک یا زبان برنامهنویسی جدیدی یاد بگیرم، سراغ یکی از این پروژهها میروم.
ادیتور متن
ما هر روزِ خدا از ادیتورهای متن استفاده می کنیم؛ اما تا حالا فکر کردهاید که اصلا ادیتورها چطور کار میکنند؟ یک دقیقه همه امکانات هیجانانگیز ادیتور محبوبتان را کنار بگذارید و سادهترین حالت یک ادیتور را در نظر بگیرید؛ اصلا چطور میشود تکستباکسی ساخت که هم یک نشانگر متحرک متن را ساپورت کند و هم بشود در آن از وارد کردن تا انتخاب و پاک کردن متن را انجام داد؟ نه نه! کامپوننت تکستباکسی که در فریمورک GUI محبوبتان هست اینجا جواب نمیدهد!
بزرگترین معمای ساخت ادیتور متن این است: متن کاربر را چطور در حافظه نگه داریم؟ اولین چیزی که به ذهن من رسید این بود که از یک آرایه استفاده کنم. اما کافی بود کاربر متن جدیدش را هر جایی غیر از آخر متن قبلی اضافه میکرد؛ آن وقت بود که ادیتورم گریبان میدرید و سر به بیابان میگذاشت. خوشبختانه، ساختار دادههای خوبی هست که میشود با یاد گرفتنشان این مسئله را حل کرد.
چالش بعدی، نحوه حرکت نشانگر در متن است. تا حالا دقت کردهاید که در ادیتورهای معمول این اتفاق چطور میافتد؟ فرض کنیم نشانگر من وسط سطر باشد. اگر فلش رو به بالا را روی کیبوردم بزنم، نشانگر کجا خواهد رفت؟ آیا در یک ستون حرکت میکند و در سطر بالایی، دقیقا در نقطه بالای جایی که در سطر پایینی بود میایستد؟ اگر سطر بالایی کوتاهتر از سطر فعلی باشد این اتفاق نخواهد افتاد. حالا فرض کنیم نشانگرم در این سطر کوتاه باشد و من دوباره فلش رو به بالا را بزنم. کافی است به سطری برسم که به اندازه کافی بلند باشد، آن وقت نشانگر دقیقا به همان ستونی میپرد که در اولین خط بود! به عبارت دیگر، انگار نشانگر برای خودش یک جور حافظه دارد که مشخصات مکان قبلیاش را آنجا نگه داشته و به محض این که بتواند، سعی می کند به آن برگردد. شما را نمیدانم، اما من فقط وقتی خواستم ادیتور بسازم تازه متوجه این جزئیات شدم.
بعد از اینکه موفق شدید یک ادیتور ساده بسازید، از شما دعوت میکنم دو تا امکان را هم به آن اضافه کنید: اول Undo/Redo و دوم Word Wrapping. من سر Undo/Redo ماجراهای هیجانانگیزی داشتم. میخواستم آن را در بهینهترین شکل ممکن بسازم. برای این کار اول سعی کردم آرایهای از حالتهای قبلی نگه دارم، بعد سراغ الگوی ممنتو (Memento) رفتم و سرانجام بالاخره به ایجاد الگوی فرمان (Command Pattern) رضایت دادم.
امکان Word Wrapping همان چیزی است که باعث میشود برای دیدن بقیه متن، نیازی به اسکرول کردن به چپ و راست نداشته باشید. با این امکان، متن در یک نقطه مشخص میشکند و به سطر بعد میرود. اضافه کردن Word Wrapping، شما را به چالش میکشد تا در هر سطر از متن، جنبههای بصری را از جنبههای مربوط به حافظه جدا کنید.
یادگرفتنیها:
- ساختار دادههای ذخیره متن: آرایه (array)، طناب، Gap Buffer و Piece Table
- عملکرد نشانگر متن و نحوه پیادهسازی آن
- الگوهای طراحی Undo/Redo: ممنتو و فرمان
- قدرت انتزاع برای جدا کردن مسئلههای بصری متن از مسائل مربوط به حافظه
منابعی برای مطالعه بیشتر:
- ادیتور متن: ساختارهای داده
- طراحی و پیادهسازی ادیتور متن Win32
- ساختارهای داده و الگوریتمها در جاوا (لینک کتاب در لیبجن)
بازیهای دو بعدی
حتی سادهترین بازیها هم برای کار کردن، اول باید الگوهای طراحی و ساختار دادههای خاصی داشته باشند. هدف این پروژه این است که بتوانید بدون درگیر شدن با چیزهایی مثل دیزاین و گرافیک، یک بازی درست و حسابی را از اول تا آخر بسازید. من پیشنهاد میکنم از یک کتابخانه گرافیکی دو بعدی استفاده کنید که فقط امکانات ضروری را داشته باشد (مثلا SDL ،SFML یا PyGame)؛ چون موتورهای بزرگ طراحی بازی بخشهای هیجانانگیز کار را از جلوی چشم شما پنهان میکنند.
در قدم اول، باید یاد بگیرید روی صفحه طرح بکشید. من هیچ ایدهای نداشتم چطور میشود این کار را کرد. برای این کار اول صفحه را پاک میکنید. بعد طرح لازم برای هر قسمت از صفحه را میکشید؛ به شکلی که با سرعت زیاد (مثلا در حد چند بار در ثانیه) پشت سر هم نمایش داده شوند. اینطوری به بیننده القا میشود که اشیاء دارند روی صفحه حرکت میکنند.
در قدم بعدی، قرار است کلی چیز در مورد لوپِ بازی یاد بگیرید. هر بازی در واقع چرخه مداومی است از طرح کشیدن، گرفتن ورودیهای کاربر و پردازش منطق بازی.
سپس یاد میگیرید چطوری ورودیهای کاربر را پردازش کنید. قبل از انجام این پروژه، من هیچوقت به ظرافتهای فشار دادن، نگه داشتن و رها کردن کلیدهای کیبورد و موس دقت نکرده بودم؛ چه برسد به نحوه پردازش چیز پیچیدهتری مثل دابلکلیک. حالا فرض کنیم پردازش را یاد گرفتیم، اصلا چند وقت یک بار باید وجود ورودی از کاربر را چک کنیم؟ اگر مرتبا در حال چک کردن باشیم، بازی قرار است همیشه هنگ کند!
چهارمین چیزی که یاد میگیرید، این است که همه شیءهای بازیتان را بسازید و مدیریتشان کنید. به عنوان مثال، چطور باید یک تعداد داینامیک از دشمنهای بازی تولید کرد؟ الگوی Factory اینجا خیلی کمک میکند.
در مرحله بعد، با منطق بازی و نحوه به کار بستن آن آشنا میشوید. موقعیت گلولهها کِی آپدیت میشود؟ چه زمانی دشمنهای بیشتری وارد صفحه میشوند؟ چطور میفهمید که دشمنی نابود شده است؟ بازی کِی تمام میشود؟ من قبل از بازی ساختن هرگز از عملگر پیمانهای (Modulo Operator) استفاده نکرده بودم؛ اما حالا کد همه بازیهایی که ساختم پر از آن است.
وقتی توانستید یک بازی اولیه را با موفقیت به کار بیندازید، میتوانید کلی چیزهای دیگر هم به آن اضافه کنید. منوی صفحه اولیه بازی را بسازید، صفحه گیماور اضافه کنید، چک کنید که سرعت بازی در کامپیوترهای مختلف یکی باشد و یاد بگیرید با کمک هوش مصنوعی، دشمنهای پردردسرتری خلق کنید. از این هم بیشتر چالش می خواهید؟ شیدر افکت (Shader Effect) اضافه کنید، صدا به بازی بیاورید و حتی ورژن چند نفره آنلاین از بازی بسازید.
یادگرفتنیها:
- طرح کشیدن روی صفحه
- مدیریت ورودیهای کاربر
- لوپ بازی
- ساخت و مدیریت تعداد داینامیکی از شیءها در بازی (مثلا با فکتوری پترن)
- ماشین حالت برای دشمنهای ساخته شده با هوش مصنوعی
- پخش صدا
- استفاده از شیدر
- شبکهسازی برای اضافه کردن امکانات آنلاین
منابعی برای مطالعه بیشتر:
- الگوهای نوشتن بازی
- ساختارهای داده برای بازینویسها (لینک لیبجن)
- نوشتن بازی هوش مصنوعی همراه با مثال (لینک لیبجن)
- هشت درسی که از منتشر کردن هشت بازی ویدیویی یاد گرفتم
کامپایلر - تاینیبیسیک
ساختن کامپایلر، آموزندهترین پروژهای است که به عمرم روی آن کار کردهام. همین حالا هم اگر عصر جمعهای وقت خالی برای کد زدن داشته باشم، به احتمال زیاد روی یک کامپایلر کار میکنم. حس خیلی قشنگی دارد که چیزی خلق کنی، بعد دیگران با کمک آفریده تو بتوانند کلی چیز بیشتر خلق کنند. فقط با ساختن یک کامپایلر، کلی اطلاعات در مورد پیچیدگیهای کامپایلرها یاد گرفتم که در حالت عادی حتی به آنها فکر هم نکرده بودم.
پیشنهاد میکنم سراغ یک زبان مختصر و بیسیکطور (مثلا Tiny Basic) بروید و کامپایلر را از اول تا آخر برای آن بسازید. بعد میتوانید آن را برای هر زبان دیگری که خوب بلدید، کامپایل کنید. مثلا میتوانید یک کامپایلر تاینیبیسیک در پایتون بنویسید که خروجیاش کد سیپلاسپلاس باشد. هیچ نیازی نیست که خروجیتان اسمبلی یا سی باشد. اصلا دوری کردن از این زبانها باعث میشود حاشیهها را ول کنید و خود کامپایلر را بچسبید.
اولین مانعی که با آن روبرو خواهید شد، این است که چطور کد ورودی را تحلیل واژهای یا Tokenize کنید. بعد قرار است کدتان را تجزیه (Parse) کنید؛ یعنی ساختار ورودیها را بررسی کنید و یک نمود درختی از کدتان بسازید. تکنیک تجزیه کاهشی بازگشتی برای این کار بینظیر است. در قدم بعدی معنای ورودیها را چک میکنید، مطمئن میشوید که کدتان معنا و مفهوم دارد و از قوانین تایپ (Type Rules) پیروی میکند. در آخر هم طبق معمول خروجی میگیرید!
برای این پروژه بهخصوص، کلی منابع آموزشی از قبل وجود دارد که حسابی کمکتان میکند. نگذارید اصطلاحات ناآشنای این وادی تهِ دلتان را خالی کنند. فقط در عرض چند روز می توانید یک کامپایلر ساده را تمام کنید. بهعلاوه، بینهایت امکان هست که میتوانید به کامپایلرتان اضافه کنید. وقتی ورژن اولیه را راه انداختید، میتوانید یک کتابخانه استاندارد هم برایش بیاورید (من در PeayBASIC فانکشن ساده گرافیک دوبعدی را اضافه کردم)، کامپایلر بهینهسازی اضافه کنید و پیامهای خطا را بهبود دهید. یادتان نرود که حتما چند تا برنامه نمونه هم با کامپایلرتان بنویسید تا بتوانید پزش را به کل دنیا بدهید!
یادگرفتنیها:
- تحلیل واژهای (Lexical Analysis)
- تحلیل نحوی (Syntactic Analysis)
- تجزیهکننده کاهشیبازگشتی (Recursive Descent Parsing)
- درخت نحوی انتزاعی (Abstract Syntax Tree)
- تحلیل معنایی (Semantic Analysis)
- کامپایلر بهینهسازی (Optimization Passes)
- تولید کد (Code Generation)
منابعی برای مطالعه بیشتر:
- آموزش ساخت مفسر
- چطور در Go، مفسر بنویسیم (لینک لیبجن)
- بیایید با هم کامپایلر بسازیم
- سورس کد PeayBASIC (گیتهاب)
مینیسیستمعامل
در گذر سالها، من بارها دیدهام که چقدر مفاهیم بنیادی سیستمعاملها را در حیطههای گوناگونی پیاده میکنم؛ از بازیها گرفته تا مدلهای پیشبینیکننده رفتار انسان. شاید سر کلاس درس، الگوریتمها و ساختار دادههای استفاده شده در سیستمعاملها به نظرتان بیفایده و انتزاعی بیایند؛ اما واقعیت این است که بیاندازه به درد میخورند. پیاده کردن یک سیستمعامل به من کمک کرد تا خیلی بهتر بفهمم که در زیربنای این ساختار عظیم و غولآسا واقعا چه میگذرد.
در مقایسه با موارد قبلی، این پروژه کمی زمان و تلاش بیشتری برای یادگیری میخواهد. همچنین از آنجا که سختافزار نقش مهمی در آن دارد، ممکن است برخی موانع هم در سر راه انجامش داشته باشید. اما اگر طبق دستورالعملهای یک راهنما یا کتاب جلو بروید، قطعا موفق میشوید یک سیستمعامل بوتشدنی بالا بیاورید که برنامههایی که خودتان نوشتهاید را اجرا کند. این کتاب رایگان نوشته همکار من و یکی از بهترین راهنماها برای این کار است.
یادگرفتنیها:
- کراسکامپایلر
- بوتلودرها
- وقفههای بایوس (BIOS interrupts)
- حالتهای مختلف x86
- مدیریت حافظه و صفحهبندی (Paging)
- زمانبندی؛ مثلا الگوریتم راند رابین (Round Robin)
- فایلسیستمها؛ مثلا FAT
منابعی برای مطالعه بیشتر:
- ویکی منابع OSDev.org
- ساخت سیستم عامل RISC-V با زبان راست (Rust)
- مفاهیم سیستمعامل (لینک لیبجن)
فکر میکنید این پروژهها بچهبازی است؟ این دوتای دیگر را امتحان کنید:
اسپردشیت (Spreadsheet)
نرمافزار اسپردشیتی مثل اکسل (Excel)، چالشهای ادیتور متن را با چالشهای کامپایلر ترکیب میکند. در جریان ساخت چنین نرم افزاری، یاد میگیرید که چطور محتوای هر خانه (Cell) را در حافظه نشان بدهید. همچنین متوجه می شوید که چطور باید مفسرها را برای زبانهای مورد استفاده در معادلات پیادهسازی کنید.
منابعی برای مطالعه بیشتر:
- گراف جهتدار غیرمدور
- پارادایم برنامهنویسی واکنشگرا
- فناوری پیادهسازی اسپردشیت (لینک آمازون)
شبیهساز کنسول بازی ویدیویی
شبیهساز (Emulator) کنسول بازی، چالشهای ساخت کامپایلر و سیستمعامل را یک جا دارد. نمیدانید چه حسی دارد که بتوانی بازی واقعی ساخت یک نفر دیگر را با شبیهساز ساخت خودت بازی کنی!
شبیهسازی یک کنسول واقعی، یعنی شما ماشین مجازیای بنویسید که ادای CPU و دیگر بخش های سختافزاری واقعی را دربیاورد. به این ترتیب میتوانید بازیهای ساخته شده برای آن کنسول را با شبیهساز خودتان بازی کنید.
من پیشنهاد میکنم قبل از رفتن سراغ یک کنسول بازی واقعی، اول CHIP-8 را شبیهسازی کنید. نینتندو، سوپر نینتندو، گیمبوی و گیمبوی ادونس همگی کاملا قابل شبیهسازی هستند، مستندسازی های فت و فراوان دارند و کلی شبیهساز متنباز هم از قبل برای آنها ساخته شده است. البته هرکدام از این کنسولها نکتههای خاص خودشان را دارند که کار را برای آدم جذاب میکند؛ مثلا ممکن است برای اجرا شدن بعضی بازیها، لازم باشد باگها/فیچرهای مستندنشده یک سختافزار بهخصوص وجود داشته باشند. PICO-8 هم گزینه دیگری است که برای خودش کنسول «فانتزی» بسیار سوددهی شده است.
منابعی برای مطالعه بیشتر:
- ساخت شبیهساز CHIP-8
- شبیهساز جاوا اسکریپت CHIP-8
- چطور گیمبوی را شبیهسازی کنیم
- سورس کد پایبوی (PyBoy) در گیتهاب
بعد از نوشتن این پست، من کلی پیشنهاد پروژه هم در هکرنیوز، ردیت، توییتر و از طریق ایمیل گرفتم که مرور آنها خالی از لطف نیست:
- ساخت یک دیتابیس از بیخ و بن
- رهگیر نور (Ray Tracer)
- کلون برنامه Paint ویندوز
- ادیتور گرافیکی وکتور
- دیکودر (Decoder) عکس
- اپلیکیشن وب چتروم
- ماشینحساب اعشار عدد پی
- ابزارهای رایج ترمینال (مثلا grep)
- سرور و کلاینت FTP
شما چه پروژههای هیجانانگیز دیگری سراغ دارید؟ ایدههایتان را در کامنتها با ما در میان بگذارید.
ترجمهای از:
"Challenging projects every programmer should try", by Austin Z. Henley
کوئرامگ مجلهای تخصصی برای توسعهدهندگان است که هر هفته با مطلبهایی در زمینه تکنولوژی، رشد فردی و آینده برنامهنویسی بهروزرسانی میشود. برای اطلاع از آخرین مطلبهای ما، میتوانید توئیتر یا کانال تلگرام ما را دنبال کنید.
مطلبی دیگر از این انتشارات
گزارش اولین سری رویداد Traceway در زمینه هوش مصنوعی و یادگیری ماشین
مطلبی دیگر از این انتشارات
سه راه شگفت انگیز برای ساده کد زدن
مطلبی دیگر از این انتشارات
یادگیری ماشین: خواندن دستخط بدون یادگیری عمیق