مسیرها
/
C++
C++
/
تمرین‌ها
/
ترول‌ها را ترول کنید
ترول‌ها را ترول کنید

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

تمرین یادگیری

مقدمه

«شمارشی»

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

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

نمونه‌ی استفاده

اصطلاح دقیق برای این نوع شمارشی، scoped enumeration است. قطعه‌کد زیر نشان می‌دهد چگونه یک enumeration با نام DeckMaterial بنویسیم. به کلیدواژه‌ی enum class و ; در پایان تعریف توجه کنید:

enum class DeckMaterial {
    maple,
    bamboo,
    plastic
};

حالا به یک تابع قیمت‌گذاری در فروشگاه اسکیت نگاه کنید و به عملگر تفکیک دامنه (::) توجه کنید که یک enumerator را از enumeration مشخص می‌کند:

double deck_price(double base_price, DeckMaterial material) {
    if(material == DeckMaterial::plastic) {
        return base_price * 0.9;
    }
    return base_price * 1.3;
}

دامنه

تصور کنید یک enumeration دوم برای جنس چرخ دارید:

enum class WheelMaterial {
    steel,
    clay,
    plastic
};

هرچند هم چرخ‌ها و هم تخته می‌توانند از plastic ساخته شوند، این دو را نمی‌توان با هم اشتباه گرفت. آن‌ها دو نوع متفاوت‌اند: plastic از نوع DeckMaterial و plastic از نوع WheelMaterial. هر enumeration، enumerators خود را در دامنه‌ی خودش دارد؛ یعنی namespace خودش. به همین دلیل است که به آن‌ها scoped enumerations می‌گویند.

Advanced

شمارشی بدون دامنه

ممکن است فکر کنید وقتی نامی مثل scoped وجود دارد، پس شمارشی‌های unscoped هم وجود دارند، و حق با شماست. Unscoped enumerations کم‌کم محبوبیت خود را از دست می‌دهند، چون همگی یک فضای نام سراسری مشترک دارند. به خاطر همین اشتراک، نمی‌توانستید دو unscoped enumerations داشته باشید که enumerators یکسانی مثل plastic در مثال بالا داشته باشند.

همچنین unscoped enumerations به‌طور ضمنی به اعداد صحیح تبدیل می‌شوند. برای دیدن یک نتیجه‌ی غافلگیرکننده به مثال زیر نگاه کنید:

enum CitrusFruits {
    lemons,  // 0
    oranges, //1
};

enum IceCream {
    walnut,  // 0
    apples,  // 1
};

bool comparison{apples == oranges};
// => true

// Example from above:
bool comparison{DeckMaterial::plastic == WheelMaterial::plastic};
// => Does not compile!

اگر می‌خواهید scoped enumerations را به اعداد صحیح تبدیل کنید، می‌توانید از static_cast<int> استفاده کنید.

دستور switch

C++ هم مانند زبان‌های دیگر، دستور switch را در اختیار می‌گذارد. دستورهای switch روش کوتاه‌تری برای نوشتن دستورهای طولانی if ... else if هستند. برای ساختن یک switch، با کلیدواژه‌ی switch شروع می‌کنیم و بعد از آن یک عدد صحیح می‌آید. سپس هر یک از شرط‌ها را با کلیدواژه‌ی case اعلام می‌کنیم. همچنین می‌توانیم یک حالت default تعریف کنیم که وقتی هیچ‌یک از شرط‌های case قبلی برقرار نبودند اجرا می‌شود. هر حالت باید با یک دستور break (یا return) پایان یابد.

int price{0};
int adults{3};
int kids{2};

switch (int group_size{adults + kids}) {
    case 1:
        price = 50;
        break;
    case 2:
        price = 70;
        break;
    default:
        price = group_size * 30;
}

عبور به مورد بعدی

یک نکته‌ی مهم درباره‌ی ساختار switch این است که کد به اجرای خود ادامه می‌دهد تا اینکه با یک دستور break (یا return) متوقف شود. این می‌تواند به رفتار غیرمنتظره‌ای منجر شود.

int adults{1};
int kids{0};
switch (int group_size{adults + kids}) {
    case 1:
        price = 50;
    case 2:
        price = 70;
    default:
        price = group_size * 30;
}
// price will be 30!

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

switch (group_size) {
case 1:
    book_room();
    break;
case 2:
case 3:
    book_apartment(group_size);
    break;
default:
    book_house(group_size);
}
// book_apartment happens when group_size is 2 or 3

دستورالعمل‌ها

دوست شما هلما یک بازی آنلاین کوچک ساخته است که به‌سرعت محبوب شد. اسم این بازی HellMath است. این جامعه‌ی کوچک چند ترول را جذب کرد که بازی و انجمن‌ها را نسبتاً ناخوشایند می‌کنند. هلما از شما خواسته است که روی یک سیستم مجوز جدید کار کنید تا دردسرسازها را جدا کنید.

انجمن از سه کنش مختلف پشتیبانی می‌کند:

  • Read
  • Write
  • Remove

چهار نوع حساب وجود دارد که هر کدام مجوزهای پیش‌فرض متفاوتی دارند:

  • مهمان‌ها: می‌توانند پست‌ها را بخوانند.
  • کاربران و ترول‌ها: می‌توانند پست‌ها را بخوانند و بنویسند.
  • ناظران: می‌توانند پست‌ها را بخوانند، بنویسند و حذف کنند؛ همه‌ی مجوزها را دارند.

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

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

1. انواع کاربر و مجوزها را تنظیم کنید.

ابتدا یک شمارش به اسم AccountStatus تعریف کنید که چهار نوع حساب را نشان دهد: troll، guest، user و mod.

سپس یک شمارش به اسم Action تعریف کنید که سه نوع مجوز را نشان دهد: read، write و remove.

2. ترول‌ها را فقط به ترول‌های دیگر نشان دهید.

هر پستی در انجمن، AccountStatus نویسنده‌ی پست را در فراداده‌ی خود ذخیره می‌کند. مطمئن شوید که پست‌های ترول‌ها فقط به ترول‌های دیگر نمایش داده می‌شوند.

هلما به یک تابع display_post نیاز دارد که دو آرگومان از نوع AccountStatus می‌گیرد و یک bool برمی‌گرداند. آرگومان اول وضعیت نویسنده‌ی پست است و آرگومان دوم وضعیت بیننده.

using namespace hellmath;
display_post(AccountStatus::troll, AccountStatus::user);
// => false
display_post(AccountStatus::mod, AccountStatus::guest);
// => true

3. مجوزهای کاربران مختلف را بررسی کنید.

هلما به راهی نیاز دارد که بررسی کند آیا یک کنش خاص برای یک کاربر مجاز است یا نه.

لطفاً تابعی به اسم permission_check پیاده‌سازی کنید که یک Action را به عنوان آرگومان اول و یک AccountStatus را برای بررسی مجوز می‌گیرد. این تابع باید بر اساس مجوزهایی که در مقدمه فهرست شده‌اند یک bool برگرداند.

permission_check(Action::remove, AccountStatus::guest);
// => false
permission_check(Action::write, AccountStatus::mod);
// => true

4. دسترسی به بازی را بدهید و بازیکنان را جفت کنید.

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

تابع valid_player_combination را پیاده‌سازی کنید که بررسی می‌کند آیا دو بازیکن می‌توانند به یک بازی مشترک بپیوندند. این تابع دو پارامتر از نوع AccountStatus دارد و یک bool برمی‌گرداند.

valid_player_combination(AccountStatus::guest, AccountStatus::mod);
// => false
valid_player_combination(AccountStatus::troll, AccountStatus::troll);
// => true

5. صف‌بندی اولویت را بسازید.

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

تابع has_priority را پیاده‌سازی کنید که دو آرگومان از نوع AccountStatus می‌گیرد و اگر و تنها اگر حساب اول اولویت اکیداً بالاتری از حساب دوم داشته باشد، true برمی‌گرداند.

has_priority(AccountStatus::guest, AccountStatus::mod);
// => false
has_priority(AccountStatus::user, AccountStatus::troll);
// => true
ویرایش از طریق GitHub این لینک در پنجره یا زبانه‌ی جدیدی باز می‌شود
C++ Exercism

آماده‌اید ترول‌ها را ترول کنید را شروع کنید؟

در Exercism ثبت‌نام کنید تا C++ را همراه با 19 مفهوم100 تمرین و مربی‌گری انسانی واقعی یاد بگیرید و در آن استاد شوید، همه‌ی این‌ها رایگان.