المسارات
/
C++
C++
/
التمارين
/
تلاعب بالمتلاعبين
تلاعب بالمتلاعبين

تلاعب بالمتلاعبين

تمرين تعلّمي

مقدمة

التعدادات

حتى الآن تعاملنا في الغالب مع الأعداد والسلاسل النصية. لكي نمثّل العالم الحقيقي، قد نحتاج إلى عدد محدود من القيم التي يمكن للمتغير أن يأخذها. قد ترغب في نوع مخصّص له بضع قيم متمايزة بأسماء مختلفة. على سبيل المثال، قد تريد في مصنع ألواح التزلج أن تكون مادة اللوح محصورة في الاختيار بين القيقب أو الخيزران أو البلاستيك فقط.

يمكنك استخدام الأعداد الصحيحة لترميز تلك القيم، لكن سيتعين عليك كتابة كود إضافي للتحقق مما إذا كانت هناك قيمة غير صالحة تأتي من النظام لمادة اللوح. من الصعب تتبّع معنى تلك الأرقام السحرية عبر الكود المصدري، وهي عرضة للالتباس. يمكن استخدام 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
};

على الرغم من أن العجلات واللوح قد يكونان معًا من البلاستيك، فلا يمكن الخلط بين الاثنين. فهما نوعان مختلفان: البلاستيك في DeckMaterial والبلاستيك في WheelMaterial. سيكون لكل enumeration ما يخصّه من enumerators في نطاقه الخاص، أي في namespace الخاصة به. ولهذا السبب تُسمّى scoped enumerations.

Advanced

التعدادات غير المحدَّدة النطاق

قد تفكّر أنه ما دام الاسم يحتوي على scoped، فلا بد أن توجد أيضًا تعدادات unscoped، وأنت محق في ذلك. أصبحت Unscoped enumerations أقل شيوعًا لأنها كلها تتشارك النطاق العام نفسه. وبسبب هذا التشارك، لا يمكن أن يكون لديك unscoped enumerations اثنان لهما نفس enumerators مثل البلاستيك في المثال أعلاه.

كذلك، تتحوّل 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 أن تُقابل نفس الجزء من الكود المراد تنفيذه. وبهذه الطريقة، في تطبيق حجز مثلًا، يمكن أن تكون الدالة المستدعاة لمجموعتي الحجم 2 و3 هي نفسها:

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. جذبت هذه الجماعة الصغيرة بعض الترولات الذين جعلوا اللعبة والمنتديات مزعجة إلى حد كبير. وقد طلبت منك هيلما العمل على نظام صلاحيات جديد لفصل مثيري المشاكل.

يدعم المنتدى ثلاثة إجراءات مختلفة:

  • قراءة
  • كتابة
  • إزالة

وهناك أربعة أنواع من الحسابات، ولكل منها صلاحيات افتراضية مختلفة:

  • الضيوف: يمكنهم قراءة المنشورات.
  • المستخدمون والترولات: يمكنهم قراءة المنشورات وكتابتها.
  • المشرفون: يمكنهم قراءة المنشورات وكتابتها وإزالتها، فلهم جميع الصلاحيات.

لاحظت هيلما أنه لا جدوى من حظر حسابات الترول. استراتيجيتها هي أن تمنحهم وهمًا بأن وقتهم «مُستثمَر جيدًا»، لكن منشوراتهم لا تُعرض إلا على الترولات الآخرين. وفي أي شيء يتطلب ترتيبًا حسب الأولوية، يأتي الترولات في آخر أي تسلسل. وعندما يدخلون لعبة، تقتصر مجموعة اللاعبين المتاحين أيضًا على الترولات الآخرين.

تُسمى هذه الممارسة الحظر الخفي.

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 تمرين، وإرشاد بشري حقيقي، وكل ذلك مجانًا.