معمارية تطبيقات المصادر المفتوحة › Battle for Wesnoth 26 / 51 السابق التالي

Battle for Wesnoth

معمارية تطبيقات المصادر المفتوحة (المجلد الأول)معركة ويسنوث

معمارية تطبيقات المصادر المفتوحة (المجلد الأول) معركة ويسنوث

Richard Shimooka وDavid White

إذا استمتعتَ بهذه الكتب، فقد يعجبك أيضًا التصميم البرمجي بالأمثلة في بايثون (Software Design by Example in Python) والتصميم البرمجي بالأمثلة في جافاسكربت (Software Design by Example in JavaScript).

يُنظر إلى البرمجة عادةً بوصفها نشاطًا مباشرًا لحلّ المسائل؛ فالمطوّر لديه متطلّبٌ فيكتب حلًّا له. أمّا الجمال فيُحكم عليه عادةً بأناقة التنفيذ التقني أو فاعليّته، وهذا الكتاب مليءٌ بأمثلةٍ ممتازة. غير أنّ الشيفرة، إلى جانب وظائفها الحاسوبية المباشرة، قد يكون لها أثرٌ عميقٌ في حياة الناس. فبإمكانها أن تلهمَ الناسَ للمشاركة وصناعة محتوى جديد. وللأسف، فهناك حواجزُ جسيمةٌ تمنع الأفرادَ من المشاركة في مشروعٍ ما.

تتطلّب معظمُ لغات البرمجة خبرةً تقنيةً كبيرةً في استخدامها، وهذا خارج متناول كثيرين. وإلى ذلك، فإنّ تحسين إمكانية الوصول إلى الشيفرة صعبٌ تقنيًّا وغيرُ ضروريٍّ لكثيرٍ من البرامج. فهو نادرًا ما ينطوي على نصوصٍ برمجية نظيفة أو حلولٍ برمجية بارعة. وتحقيقُ إمكانية الوصول يتطلّب قدرًا كبيرًا من التقدّم المسبق في تصميم المشروع والبرنامج، وهو ما يتعارض غالبًا مع معايير البرمجة المعتادة. فضلًا عن ذلك، فإنّ معظمَ المشاريع تعتمد على فريقٍ قائمٍ من المحترفين المتمرّسين، يُتوقَّع منهم العملُ بمستوى معقولٍ من الجودة. فهم لا يحتاجون إلى مواردَ برمجيةٍ إضافية. وهكذا تصير إمكانيةُ الوصول إلى الشيفرة أمرًا متأخّرًا، إن نظر فيها أحدٌ أصلًا.

حاول مشروعُنا، معركةُ ويسنوث (Battle for Wesnoth)، معالجةَ هذه المسألة منذ بداياته. البرنامجُ لعبةُ استراتيجيا خياليةٌ قائمةٌ على الأدوار، أُنتجت وفق نموذجٍ مفتوح المصدر قائمٍ على رخصة GPL2. وقد حقّق نجاحًا متوسّطًا، إذ تجاوز عددُ تنزيلاته أربعةَ ملايين عند كتابة هذا الفصل. ورغم أنّ هذا رقمٌ لافت، فإنّنا نؤمن بأنّ جمال مشروعنا الحقيقي يكمن في نموذج التطوير الذي مكّن مجموعةً من المتطوّعين ذوي مستويات مهارةٍ متباعدةٍ جدًّا من التفاعل بعضهم مع بعض إنتاجيةً.

لم يكن تحسينُ إمكانية الوصول هدفًا غامضًا وضعه المطوّرون، بل رُئي عنصرًا جوهريًّا لبقاء المشروع. فمقاربةُ ويسنوث المفتوحة تعني أنّ المشروع لا يستطيع أن يتوقّع في البدء أعدادًا كبيرةً من المطوّرين شديدي المهارة. وجعلُ المشروع في متناول عددٍ واسعٍ من المساهمين، متفاوتين في مستويات مهارتهم، يضمن جداه على المدى الطويل.

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

يقدّم الجزءُ الأول من هذا الفصل لمحةً عامةً عن برمجة المشروع، متضمّنًا لغتَه وتبعياته ومعماريتَه. وسيركّز الجزءُ الثاني على لغة تخزين البيانات الفريدة الخاصة بويسنوث، المعروفة باسم لغة ترميز ويسنوث (Wesnoth Markup Language، أو WML). وسيشرح الوظائفَ المحدّدة لـ WML، مع تركيزٍ خاصٍّ على آثارها في وحدات اللعب داخل اللعبة. ويغطّي القسمُ التالي تنفيذَ اللعب الجماعي والبرامج الخارجية. ويختم الفصلُ بعضَ الملاحظات الختامية حول بنيتنا وتحدّيات توسيع المشاركة.

25.1. لمحةٌ عامةٌ عن المشروع

محرّكُ ويسنوث الأساسي مكتوبٌ بـ C++، ويبلغ مجموعُه نحو 200,000 سطر وقت صدور هذا الكتاب. ويمثّل ذلك محرّكَ اللعبة الأساسي، أي نحو نصف قاعدة الشيفرة من دون أيّ محتوى. ويتيح البرنامجُ أيضًا تعريفَ محتوى اللعبة بلغة بياناتٍ فريدةٍ اسمها لغة ترميز ويسنوث (WML). ويأتي اللعبةَ نحو 250,000 سطرٍ آخر من شيفرة WML. وقد تغيّرت هذه النسبة على مدى عمر المشروع. فمع نضج البرنامج، أُعيدت كتابةُ محتوى اللعبة الذي كان مضمَّنًا في شيفرة C++ بشكلٍ متزايد، بحيث صار WML هو ما يعرّف سلوكَه. ويقدّم الشكل 25.1 صورةً تقريبيةً لمعمارية البرنامج؛ فالمساحاتُ الخضراء يتولّى صيانتها مطوّرو ويسنوث، بينما تمثّل المساحاتُ البيضاء التبعيات الخارجية.

معمارية البرنامج

الشكل 25.1: معمارية البرنامج

عمومًا، يحاول المشروعُ تقليلَ التبعيات في معظم الحالات، وذلك لتعظيم قابلية نقل التطبيق. ولهذا مكسبٌ إضافيٌّ إذ يقلّل من تعقيد البرنامج، ويخفّض حاجةَ المطوّرين إلى تعلّم تفرّقات عددٍ كبيرٍ من واجهات البرمجة للتطبيقات التابعة لجهاتٍ أخرى (third-party APIs). وفي الوقت نفسه، فإنّ الاستخدامَ المتحفّظ لبعض التبعيات يمكن أن يحقّق الأثر نفسه فعلًا. فمثلًا، تستخدم ويسنوث طبقةَ الوسائط المباشرة البسيطة (Simple DirectMedia Layer، أو SDL) في الفيديو وعمليات الإدخال/الإخراج (I/O) ومعالجة الأحداث. وقد اختيرت لأنّها سهلةٌ في الاستخدام وتوفّر واجهةَ إدخال/إخراجٍ مشتركةً عبر منصّاتٍ كثيرة. وهذا يتيح للبرنامج أن يكون قابلًا للنقل إلى مجموعةٍ واسعةٍ من المنصّات، بدلًا من البديل الذي يقتضي البرمجةَ ضدّ واجهاتٍ برمجيةٍ خاصّةٍ مختلفٍ لكل منصّة. غير أنّ لهذا ثمنًا؛ فاستغلالُ بعض المزايا الخاصّة بالمنصّة يصعب أكثر. ويملك SDL أيضًا عائلةً مرافقةً من المكتبات التي تستخدمها ويسنوث لأغراضٍ متنوّعة:

  • SDL_Mixer للصوت والمؤثّرات الصوتية
  • SDL_Image لتحميل PNG وصورٍ بصيغٍ أخرى
  • SDL_Net لعمليات الإدخال/الإخراج عبر الشبكة

وإضافةً إلى ذلك، تستخدم ويسنوث عدةَ مكتباتٍ أخرى:

  • Boost لمجموعةٍ متنوّعةٍ من مزايا C++ المتقدّمة
  • Pango مع Cairo للخطوط المُعرَّبة
  • zlib للضغط
  • Python وLua لدعم السكربتات
  • GNU gettext للتعريب

في جميع أنحاء محرّك ويسنوث، الاستخدامُ لكائنات WML—أي قواميسَ سلاسلَ ذات عُقدٍ فرعية—واسعٌ إلى حدٍّ كبير. فالكثيرُ من الكائنات يمكن بناؤها من عقدة WML، كما تستطيع تسلسلَ نفسها (serialize) إلى عقدة WML. وتحتفظ بعضُ أجزاء المحرّك بالبيانات في صيغة قاموس WML هذا، وتفسّرها مباشرةً بدلًا من تحليلها (parsing) إلى بنيةِ بياناتٍ في C++.

تستخدم ويسنوث عدةَ أنظمةٍ فرعيةٍ مهمّة، معظمُها مكتتفٍ بذاته إلى أقصى حدٍّ ممكن. ولهذه البنية المقسّمة مزايا فيما يخصّ إمكانية الوصول. فالطرفُ المهتمّ يستطيع بسهولة العملَ على الشيفرة في مجالٍ بعينه وإدخالَ تغييراتٍ من دون الإضرار ببقية البرنامج. وتشمل التقسيماتُ الرئيسة:

  • محلِّل WML مع مُعالِجٍ مسبق (preprocessor)
  • وحداتُ إدخال/إخراجٍ أساسيةٍ تجرّد المكتباتَ واستدعاءاتِ النظام—وحدةُ فيديو، ووحدةُ صوت، ووحدةُ شبكة
  • وحدةُ واجهةِ المستخدم الرسومية (GUI) تتضمّن تنفيذاتِ عناصرِ واجهةٍ (widgets) للأزرار والقوائم والقوائم المنسدلة وغيرها
  • وحدةُ عرضٍ (display) لتصيير رقعةِ اللعبة والوحدات والحركات وما إلى ذلك
  • وحدةُ ذكاءٍ اصطناعي (AI)
  • وحدةُ إيجادِ المسارات (pathfinding) تتضمّن كثيرًا من دوالّ الخدمات لمعالجة رقعةِ لعبٍ سداسيةِ الأضلاع
  • وحدةُ توليدِ الخرائط (map generation) لتوليد أنواعٍ مختلفةٍ من الخرائط العشوائية

وهناك أيضًا وحداتٌ مختلفةٌ تتحكّم في أجزاءٍ مختلفةٍ من تدفّق اللعبة:

  • وحدةُ شاشة العنوان (titlescreen)، للتحكّم في عرض شاشة العنوان.
  • وحدةُ القصة (storyline)، لعرض المقاطعِ المشهدية (cut-scene).
  • وحدةُ الردهة (lobby)، لعرض وإتاحة إعدادِ المباريات على خادم اللعب الجماعي.
  • وحدةُ «لعب المباراة» (play game) التي تتحكّم في اللعب الرئيس.

ووحدةُ «لعب المباراة» ووحدةُ العرض الرئيسيتان هما الأكبر داخل ويسنوث. وغرضُهما أقلُّ ما يكون محدَّدًا، لأنّ وظيفتَهما متغيّرةٌ باستمرار، وهكذا يصعب وضعُ مواصفةٍ واضحةٍ لهما. ونتيجةً لذلك، تعرّضت الوحداتُ كثيرًا، طوال تاريخ البرنامج، لخطر الإصابة بالنمط المضادّ «Blob»—أي أن تصير أجزاءً ضخمةً مهيمنةً بسلوكٍ غير محدَّد المعالم. وتُراجَع الشيفرةُ في وحدتَي العرض ولعب المباراة بانتظام لمعرفة ما إذا كان يمكن فصلُ أيّ جزءٍ منها في وحدةٍ مستقلّة.

وتوجد أيضًا ميزاتٌ مساعِدةٌ تندرج ضمن المشروع العامّ، لكنّها منفصلةٌ عن البرنامج الرئيس. ومن ذلك خادمٌ للّعب الجماعي ييسّر ألعابَ الشبكة الجماعية، إضافةً إلى خادمٍ للمحتوى يتيح للمستخدمين رفعَ محتواهم إلى خادمٍ مشتركٍ ومشاركته مع غيرهم. وكلاهما مكتوبٌ بـ C++.

25.2. لغة ترميز ويسنوث

بوصفه محرّكَ لعبةٍ قابلًا للتوسّع، تستخدم ويسنوث لغةَ بياناتٍ بسيطةً لتخزين بيانات اللعبة وتحميلها كلِّها. ورغم أنّ XML رُشِّح في البداية، فقد قررنا أنّنا نريد شيئًا أكثر ملاءمةً قليلًا للمستخدمين غير التقنيين، وأكثر تساهلاً في التعامل مع البيانات البصرية. وهكذا طوّرنا لغتنا الخاصة للبيانات، وسمّيناها لغةَ ترميز ويسنوث (WML). وقد صُمِّمت وهي في ذهن أضعفِ المستخدمين مهارةً: إذ كان الأملُ أن يستطيع حتى من يجد Python أو HTML مرهقَ الفهم أن يتعاطى مع ملف WML. وكلُّ بياناتِ لعبةِ ويسنوث مخزَّنةٌ بصيغة WML، بما في ذلك تعريفاتُ الوحدات والحملات (campaigns) والمشاهد (scenarios) وتعريفاتُ واجهة المستخدم الرسومية، وسائرُ إعدادات منطق اللعبة.

تتشارك WML مع XML السماتَ الأساسيةَ نفسها: العناصر والسمات، وإن كانت لا تدعم النصّ داخل العناصر. أمّا سماتُ WML فتُمثَّل مجرّدَ قاموسٍ يربط السلاسلَ بالسلاسل، ويتولّى منطقُ البرنامج تفسيرَ السمات. وإليك مثالًا بسيطًا على WML هو تعريفٌ مختصرٌ لوحدة «المقاتل الأليف» (Elvish Fighter) داخل اللعبة:

[unit_type]
    id=Elvish Fighter
    name= _ "Elvish Fighter"
    race=elf
    image="units/elves-wood/fighter.png"
    profile="portraits/elves/fighter.png"
    hitpoints=33
    movement_type=woodland
    movement=5
    experience=40
    level=1
    alignment=neutral
    advances_to=Elvish Captain,Elvish Hero
    cost=14
    usage=fighter
    {LESS_NIMBLE_ELF}
    [attack]
        name=sword
        description=_"sword"
        icon=attacks/sword-elven.png
        type=blade
        range=melee
        damage=5
        number=4
    [/attack]
[/unit_type]

ولأنّ التعريب مهمٌّ في ويسنوث، توفّر WML دعمًا مباشرًا له: فقيمُ السمات التي تسبقها شرطةٌ سفلية قابلةٌ للترجمة. وأيُّ سلسلةٍ قابلةٍ للترجمة تُحوَّل باستخدام GNU gettext إلى النسخة المترجَمة من السلسلة عند تحليل WML.

وبدلًا من امتلاك عدّة مستندات WML مختلفة، تختار ويسنوث نهجَ تقديم جميع بيانات اللعبة الرئيسة إلى محرّك اللعبة في مستندٍ واحدٍ فحسب. وهذا يتيح متغيّرًا عامًّا واحدًا يحمل المستند، وحين تُحمَّل اللعبة تُحمَّل، مثلًا، جميعُ تعريفات الوحدات بالبحث عن عناصرَ اسمها unit_type داخل عنصر units.

ورغم أنّ كلَّ البيانات مخزَّنةٌ في مستند WML واحدٍ من الناحية المفاهيمية، فإنّ وضعَها كلِّها في ملفٍ واحدٍ سيكون مرهقًا. لذلك تدعم ويسنوث مُعالِجًا مسبقًا (preprocessor) يُطبَّق على كلّ WML قبل تحليله. ويتيح هذا المُعالِجُ المسبقُ لملفٍ ما أن يضمّ محتوىَ ملفٍ آخر، أو محتوىَ دليلٍ كامل. فمثلًا:

{gui/default/window/}

سيضمّ جميعَ ملفات .cfg الموجودة في gui/default/window/.

ولأنّ WML قد تصبح مُطوَّلةً جدًّا، يتيح المُعالِجُ المسبق أيضًا تعريفَ ماكروهات (macros) للاختصار. فمثلًا، الاستدعاءُ {LESS_NIMBLE_ELF} في تعريف «المقاتل الأليف» هو استدعاءُ ماكرو يجعل بعضَ وحدات الألف أقلَّ رشاقةً في ظروفٍ معيّنة، كأن تكون مُنشأةً في غابة:

#define LESS_NIMBLE_ELF
    [defense]
        forest=40
    [/defense]
#enddef

ولهذا التصميم ميزةُ جعل المحرّك غيرَ مبالٍ بكيفية تقسيم مستند WML إلى ملفات. فمن مسؤولية كتّاب WML أن يقرّروا كيفيةَ تنظيم بيانات اللعبة كلِّها وتقسيمها إلى ملفاتٍ وأدلةٍ مختلفة.

وحين يحمّل محرّكُ اللعبة مستند WML، يعرّف أيضًا بعضَ رموز المُعالِج المسبق وفق إعدادات اللعبة المختلفة. فمثلًا، تستطيع حملةٌ في ويسنوث تعريفَ إعداداتٍ مختلفةٍ للصعوبة، بحيث يؤدّي كلُّ إعدادِ صعوبةٍ إلى تعريف رمزٍ مختلفٍ للمُعالِج المسبق. ومثالٌ شائعٌ على تغيير الصعوبة هو تغيير مقدار الموارد الممنوحة للخصم (والمُمثَّلة بالذهب). ولتسهيل ذلك، يُعرَّف ماكرو WML على النحو التالي:

#define GOLD EASY_AMOUNT NORMAL_AMOUNT HARD_AMOUNT
  #ifdef EASY
    gold={EASY_AMOUNT}
  #endif
  #ifdef NORMAL
    gold={NORMAL_AMOUNT}
  #endif
  #ifdef HARD
    gold={HARD_AMOUNT}
  #endif
#enddef

ويمكن استدعاءُ هذا الماكرو، مثلًا، بالشكل {GOLD 50 100 200} داخل تعريف الخصم، لتحديد مقدار الذهب الذي يملكه الخصم بحسب مستوى الصعوبة.

ولأنّ WML تُعالَج شرطًا، فإنّ أيَّ رموزٍ تُمرَّر إلى مستند WML وتغيّرت أثناء تنفيذ محرّك ويسنوث تفرض إعادةَ تحميل المستند كلّه ومعالجته. فمثلًا، حين يبدأ المستخدمُ اللعبة، يُحمَّل مستند WML وتُحمَّل الحملاتُ المتاحة وغيرها. لكنّ المستخدمَ إن اختار بدءَ حملةٍ واختار مستوى صعوبةٍ معيّنًا—السهل مثلًا—فسيلزم إعادةُ تحميل المستند كلّه مع تعريف EASY.

وهذا التصميمُ مريحٌ من حيث إنّ مستندًا واحدًا يحوي كلَّ بيانات اللعبة، وأنّ الرموز تتيح ضبطَ مستند WML بسهولة. غير أنّ المشروع، بوصفه ناجحًا، يتوفّر له محتوى متزايدٌ باستمرار، بما في ذلك الحجم الكبير من المحتوى القابلُ للتنزيل—وكلُّه يُدرَج في شجرة المستند الرئيسة—مما يعني أنّ مستند WML يبلغ أحجامًا بالميغابايت. وقد صار هذا مسألةَ أداءٍ بالنسبة إلى ويسنوث: فقد يستغرق تحميلُ المستند ما يصل إلى دقيقةٍ على بعض الحواسيب، ما يسبّب تأخيرًا داخل اللعبة كلّما احتاج المستندُ إلى إعادة التحميل. كما أنّه يستهلك مقدارًا كبيرًا من الذاكرة. وقد اتُّخذت بعضُ الإجراءات لمقابلة ذلك: فحين تُحمَّل حملةٌ ما، يُعرَّف في المُعالِج المسبق رمزٌ فريدٌ لتلك الحملة. وهذا يعني أنّ أيَّ محتوى خاصٍّ بتلك الحملة يمكن تقييدُه بـ #ifdef بحيث لا يُستخدَم إلا عند الحاجة إلى تلك الحملة.

وإضافةً إلى ذلك، تستخدم ويسنوث نظامَ تخزينٍ مؤقّت (caching) يخزّن النسخةَ المعالَجةَ كليًّا من مستند WML لجملةٍ معيّنةٍ من التعريفات المفتاحية. وبطبيعة الحال، يجب على نظام التخزين المؤقّت هذا أن يفحصَ الطابعَ الزمني (timestamp) لجميع ملفات WML، بحيث إن تغيّر أيٌّ منها أُعيد توليدُ المستند المخزَّن.

25.3. الوحداتُ في ويسنوث

أبطالُ ويسنوث هم وحداتُه. فقد ينزل «المقاتل الأليف» (Elvish Fighter) و«الساحر الأليف» (Elvish Shaman) في مواجهة «محارب الغول» (Troll Warrior) و«مشاة الأوق» (Orcish Grunt). تتشارك جميعُ الوحدات السلوكَ الأساسي نفسه، لكنّ كثيرًا منها له قدراتٌ خاصةٌ تغيّر التدفّقَ الطبيعي للّعب. فمثلًا، يستعيد الغولُ جزءًا من صحّته كلَّ دور، ويبطئ الساحرُ الأليف خصومَه بجذرٍ مُتشابك، ويصبح الـ Wose غيرَ مرئيٍّ داخل الغابة.

ما أفضلُ طريقةٍ لتمثيل هذا في محرّك؟ من المغريّ إنشاءُ صنفٍ أساسيّ unit بـ C++، تُشتقّ منه أنواعٌ مختلفةٌ من الوحدات. فمثلًا، يمكن أن يُشتقّ صنفُ wose_unit من unit، وأن يكون في unit دالةٌ افتراضية bool is_invisible() const تعيد false، فيتجاوزها wose_unit لتعيد true إن كانت الوحدةُ في الغابة.

لَما كان هذا النهجُ سينجح إلى حدٍّ معقولٍ في لعبةٍ ذات قواعدَ محدودة. لكنّ ويسنوث لعبةٌ كبيرةٌ إلى حدٍّ بعيد، وهذا النهج ليس سهلَ التوسعة. فإذا أراد شخصٌ ما إضافةَ نوعٍ جديدٍ من الوحدات في هذا النهج، لكان عليه إضافةُ صنف C++ جديدٍ إلى اللعبة. وإلى ذلك، لا يسمح بدمج الخصائصِ المختلفة دمجًا جيّدًا: فماذا لو كانت لديك وحدةٌ تستعيد الصحّة، وتستطيع إبطاءَ الأعداء بشبكة، وتكون غيرَ مرئيةٍ في الغابة؟ لكان عليك كتابةُ صنفٍ جديدٍ بالكامل يكرّر شيفرةَ الأصناف الأخرى.

لا يستخدم نظامُ الوحدات في ويسنوث الوراثةَ (inheritance) إطلاقًا لإنجاز هذه المهمّة. بل يستخدم صنفَ unit لتمثيل نُسخ الوحدات، وصنفَ unit_type الذي يمثّل الخصائصَ غيرَ القابلة للتغيير التي تتشاركها جميعُ وحدات نوعٍ ما. ويملك صنفُ unit مرجعًا إلى نوع الكائن الذي هو عليه. وتُخزَّن جميعُ كائنات unit_type الممكنة في قاموسٍ عامٍّ يُحمَّل عند تحميل مستند WML الرئيس.

ولكلّ نوعِ وحداتٍ قائمةٌ بالقدرات التي تملكها تلك الوحدة. فمثلًا، تملك الغزلانُ (Trolls) قدرةَ «التجدد» (regeneration) التي تجعلها تشفي نفسها كلَّ دور. ويملك «المناوشُ السّوريّ» (Saurian Skirmisher) قدرةَ «المناوشة» (skirmisher) التي تتيح له العبورَ عبر خطوط العدو. والتعرّفُ على هذه القدرات مدمجٌ في المحرّك—فمثلًا، تتحقّق خوارزمياتُ إيجاد المسارات ممّا إذا كانت الوحدة تحمل رايةَ «skirmisher» لترى إن كانت تستطيع التحرّكَ بحرّيةٍ عبر خطوط الأعداء. ويتيح هذا النهجُ لفردٍ ما إضافةَ وحداتٍ جديدة، تحمل أيَّ توليفةٍ من القدرات التي يوفّرها المحرّك، بتحرير WML وحدها. وبالطبع، لا يسمح بإضافة قدراتٍ وسلوكياتِ وحداتٍ جديدةٍ تمامًا من دون تعديل المحرّك.

وإضافةً إلى ذلك، قد تملك كلُّ وحدةٍ في ويسنوث أيَّ عددٍ من طرائق الهجوم. فمثلًا، يملك «الرامي الأليف» (Elvish Archer) هجومَ قوسٍ طويلَ المدى، إضافةً إلى هجومِ سيفٍ قصير المدى. ويحقّق كلٌّ منهما مقدارَ ضررٍ وخصائصَ مختلفة. لتمثيل الهجوم، هناك صنفُ attack_type، ولكلِّ نسخةٍ من unit_type قائمةُ attack_types ممكنة.

ولمنح كلِّ وحدةٍ طابعًا أكثرَ تميّزًا، توفّر ويسنوث خاصيةً تُسمّى السمات (traits). فعند التجنيد، تُسنَد إلى معظمِ الوحدات سمتان عشوائيًّا من قائمةٍ مُعرَّفةٍ مسبقًا. فمثلًا، الوحدةُ القوية تُحدث ضررًا أكبرَ في هجماتِها القريبة، بينما تحتاج الوحدةُ الذكية قدرًا أقلَّ من الخبرة قبل أن «ترتقي مستواها» (levels up). وإلى ذلك، من الممكن أن تنال الوحداتُ أثناءَ اللعب تجهيزاتٍ تجعلها أقوى. فمثلًا، قد توجد سيفٌ تستطيع وحدةٌ التقاطُه يجعل هجماتِها تُحدث ضررًا أكبر. ولتنفيذ السمات والتجهيزات، تسمح ويسنوث بـ«تعديلات» (modifications) على الوحدات، وهي تغييراتٌ مُعرَّفةٌ في WML لإحصاءاتِ الوحدة. وقد يُطبَّق التعديلُ حتى على أنواعٍ بعينها من الهجمات. فمثلًا، تمنح سِمةُ القوةِ الوحداتِ القويةَ ضررًا أكبرَ عند الهجوم في الاشتباك القريب، لكنّها لا تفعل ذلك عند استخدام ضربةٍ بعيدة المدى.

السماحُ بتكوين سلوكِ الوحدات تحكّمًا كاملًا عبر WML هدفٌ يستحقّ الثناء، ومن المفيد التمعّنُ في سبب عدم بلوغِ ويسنوث هذا الهدف قطّ. فلابدّ أن تكون WML أكثرَ مرونةً بكثير ممّا هي عليه كي تسمح بأيِّ سلوكٍ للوحدات. فبدلًا من أن تكون لغةً موجَّهةً للبيانات، لابدّ من توسيع WML لتصبح لغةَ برمجةٍ متكاملة، وسيكون ذلك مُرهِبًا لكثيرٍ من المساهمين الطامحين.

وإضافةً إلى ذلك، يتعرّف ذكاءُ ويسنوث الاصطناعي (AI)، وهو مطوَّرٌ بـ C++، على القدراتِ الموجودة في اللعبة. فهو يأخذ في الحسبان التجدّدَ وعدمَ الرؤية وما إلى ذلك، ويحاول توجيهَ وحداتِه لاغتنام أقصى استفادةٍ من هذه القدرات المختلفة. وحتى لو أمكن إنشاءُ قدرةِ وحدةٍ باستخدام WML، فإنّ جعلَ الذكاء الاصطناعي ذكيًّا بالقدر الكافي للتعرّف على هذه القدرة والاستفادة منها يظلّ صعبًا. فتنفيذُ قدرةٍ من دون أن يأخذها الذكاءُ الاصطناعي في الحسبان لن يكون تنفيذًا مُرضيًّا حقًّا. وعلى نحوٍ مماثل، فإنّ تنفيذَ قدرةٍ في WML ثمّ تعديلَ الذكاء الاصطناعي في C++ ليأخذها في الحسبان سيكون تنفيذيًّا مرتجِفًا. وعليه، فإنّ جعلَ الوحدات قابلةً للتعريف في WML، مع بقاءِ القدرات موصولةً بصلابةٍ داخل المحرّك، يُعدّ حلًّا وسطًا معقولًا يعمل على أفضل وجهٍ مع متطلّباتِ ويسنوث بعينها.

25.4. تنفيذُ اللعب الجماعي في ويسنوث

يستخدم تنفيذُ اللعب الجماعي في ويسنوث أبسطَ النهج الممكن لتنفيذ اللعب الجماعي. فهو يحاول التخفيفَ من احتمال هجماتٍ خبيثةٍ على الخادم، لكنّه لا يحاول جدّيّةً منعَ الغش. وأيُّ حركةٍ تُجرى في لعبةٍ من ويسنوث—تحريكُ وحدة، ومهاجمةُ عدو، وتجنيدُ وحدة، وما إلى ذلك—يمكن حفظُها كعقدة WML. فمثلًا، قد يُحفظ أمرُ تحريك وحدةٍ في WML على هذا النحو:

[move]
    x="11,11,10,9,8,7"
    y="6,7,7,8,8,9"
[/move]

وهذا يبيّن المسارَ الذي تسلكه وحدةٌ نتيجةً لأوامر اللاعب. وتتيح اللعبةُ عندئذٍ آليةً لتنفيذ أيِّ أمر WML يُعطى لها. وهذا مفيدٌ جدًّا إذ يعني أنّه يمكن حفظُ إعادةِ تشغيلٍ كاملةٍ بتخزين الحالة الابتدائية للعبة ثمّ جميعَ الأوامر اللاحقة. والقدرةُ على إعادة تشغيل الألعاب مفيدةٌ للاعبين كي يشاهد كلٌّ منهم الآخر وهو يلعب، كما تساعد في بعض أنواع تقارير العلل.

قرّرنا أن يركّز المجتمعُ على الألعابِ الودّية العارضة في تنفيذ اللعب الجماعي عبر الشبكة. فبدلًا من شنّ معركةٍ تقنيةٍ ضدّ قرصنةٍ منحرفةٍ تحاول اختراقَ أنظمة منع الغش، لن يبذل المشروعُ ببساطةٍ جهدًا كبيرًا في منع الغش. وقد بيّن تحليلُ ألعابٍ جماعيةٍ أخرى أنّ أنظمةَ التصنيف التنافسي مصدرٌ رئيسي للسلوك المنحرف. ومنعُ هذه الوظائف عمدًا على الخادم قلّل كثيرًا من دافعِ الأفراد إلى الغش. وإلى ذلك، يحاول المشرفون تشجيعَ مجتمعِ لعبٍ إيجابيٍّ يبني فيه الأفرادُ علاقاتَ شخصيةً مع لاعبين آخرين ويلعبون معهم. وهذا يضع وزنًا أكبرَ على العلاقات بدلًا من المنافسة. وقد عُدَّت نتيجةُ هذه الجهود ناجحة، إذ بقيت محاولاتُ اختراق اللعبةِ خبيثةً إلى حدٍّ كبيرٍ معزولةً ومنعزلةً عن بقية المجتمع.

يتكوّن تنفيذُ اللعب الجماعي في ويسنوث من بنيةٍ نمطيةٍ من نوع العميل-الخادم. فخادمٌ يُعرف باسم wesnothd يقبل الاتصالاتِ من عميلِ ويسنوث، ويرسل إلى العميل ملخّصًا بالمباريات المتاحة. ويعرض ويسنوث للاعبٍ ما «ردهةً» (lobby) يختار منها الانضمامَ إلى مباراةٍ أو إنشاءَ مباراةٍ جديدةٍ ينضمّ إليها آخرون. وبعد أن يدخل اللاعبون المباراةَ وتبدأ، تولّد كلُّ نسخةٍ من ويسنوث أوامرَ WML تصف الأفعالَ التي يقوم بها اللاعب. وتُرسَل هذه الأوامرُ إلى الخادم، ثمّ يمرّرها الخادمُ بدوره إلى جميع العملاء الآخرين في المباراة. ويؤدي الخادمُ إذن دورَ مرحِّلٍ رقيقٍ جدًّا وبسيط. ويُستخدَم نظامُ إعادة التشغيل على العملاء الأخرى لتنفيذ أوامر WML. ولأنّ ويسنوث لعبةٌ قائمةٌ على الأدوار، تُستخدَم بروتوكولاتُ TCP/IP في جميع اتصالات الشبكة.

ويتيح هذا النظامُ للمُراقبين مشاهدةَ المباراة بسهولة. فيمكن للمراقبِ الانضمامُ إلى مباراةٍ جارية، وعندئذٍ يرسل الخادمُ WML الذي يمثّل الحالةَ الابتدائية للمباراة، ثمّ سجلًّا لجميع الأوامر التي نُفِّذت منذ بدء المباراة. وهذا يتيح للمراقبين الجدد أن يعرفوا حالةَ المباراة. فيمكنهم رؤيةُ سجلّ المباراة، وإنّ ذلك يستغرق وقتًا حتى يبلغ المراقبُ الموضعَ الحالي للمباراة—فتُقدَّم أوامرُ السجلّ إلى الأمام بسرعة، لكنّ ذلك يستغرق وقتًا على أيّ حال. والبديلُ هو أن يُولِّد أحدُ العملاء لقطةً (snapshot) للحالة الحالية للمباراة بصيغة WML ويرسلها إلى المراقب الجديد؛ غير أنّ هذا النهجَ يُحمِّل العملاءَ حمولةً زائدةً (overhead) بحسب عدد المراقبين، وقد يُسهِّل هجمات حجب الخدمة (denial-of-service) بانضمام عددٍ كبيرٍ من المراقبين إلى مباراة.

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

25.5. الخاتمة

نؤمن بأنّ جمال «معركة ويسنوث» بوصفها برنامجًا يكمن في جعل البرمجة في متناولِ أفرادٍ من تنوّعٍ واسع. وقد اتُّخذ المشروعُ لتنفيذ هذا الهدف تنازلاتٍ لا تبدو أنيقةً البتّة في الشيفرة. ويجدرُ بالذكر أنّ كثيرًا من أكثر مطوّري المشروع موهبةً ينظرون إلى WML بعينِ الرفض بسبب بناء جملتها غيرِ الكفء. غير أنّ هذا التنازلَ مهّد لأحد أكبرِ نجاحات المشروع. واليوم يستطيع ويسنوث أن يفاخرَ بمئات الحملات (campaigns) والحقب (eras) التي صنعَها المستخدمون، وقد صنعَها في معظمها مستخدمو خبرةٍ برمجيةٍ قليلةٍ أو معدومة. وإلى ذلك، ألهم عددًا من الناس اتّخاذَ البرمجة مهنةً لهم، مستخدمين المشروعَ أداةَ تعلّم. وهذه إنجازاتٌ ملموسةٌ قلَّ أن تضاهيها برامجُ أخرى.

ومن أبرز الدروس التي ينبغي أن يخرج بها القارئُ من جهودِ ويسنوث أن يفكّر في التحدياتِ التي يواجهها المبرمجون الأقلُّ مهارةً. فهذا يتطلّب وعيًا بما يمنع المساهمينَ من ممارسة البرمجة فعلًا وتطوير مهاراتهم. فمثلًا، قد يريد فردٌ ما المساهمةَ في البرنامج من دون أن يملك أيَّ مهارةٍ برمجية. فالمحرّراتُ المتخصّصةُ مثل emacs أو vim لها منحنى تعلُّمٍ كبيرٌ قد يكون مُرهِبًا لمثل هذا الفرد. وهكذا صُمِّم WML ليسمح لمحرِّرِ نصوصٍ بسيطٍ بأن يفتح ملفاته، فيمنح أيَّ شخصٍ الأدواتَ اللازمة للمساهمة.

غير أنّ زيادةَ إمكانية الوصول إلى قاعدة شيفرةٍ ما ليست هدفًا سهلَ التحقيق. فليست هناك قواعدٌ قاطعةٌ لزيادة إمكانية الوصول إلى الشيفرة. بل إنّها تتطلّب توازنًا بين اعتباراتٍ مختلفة، قد تكون لها عواقبُ سلبيةٌ يجب أن يكون المجتمعُ على وعيٍ بها. ويتّضح ذلك في كيفية تعامل البرنامج مع التبعيات. ففي بعض الحالات، قد تزيدُ التبعياتُ في واقع الأمر من حواجز المشاركة، بينما في حالاتٍ أخرى تتيحُ للناس المساهمةَ بمَحَبٍّ أكبر. وكلُّ مسألةٍ يجب أن تُنظر على أساس حالتها.

وينبغي ألّا نبالغ في بعض نجاحاتِ ويسنوث كذلك. فقد استفاد المشروعُ من بعض المزايا التي لا يسهل على برامجٍ أخرى تكرارَها. وجعلُ الشيفرة في متناولِ جمهورٍ أوسع ثمرةٌ جزئيةٌ لطبيعة البرنامج. فالبرنامجُ مفتوحُ المصدر أتاح لويسنوث عدّةَ مزاياٍ في هذا الصدد. فقانونًا، تسمح رخصةُ GNU لأحدهم أن يفتح ملفًّا موجودًا، ويفهم كيف يعمل، ويجري تغييرات. ويُشجَّع الأفرادُ على التجريبِ والتعلّمِ والمشاركة داخل هذه الثقافة، وقد لا يكون ذلك مناسبًا لبرامجٍ أخرى. غير أنّنا نأمل أن تكون هناك عناصرُ بعينها قد تثبت فائدتَها لجميع المطوّرين وأن تساعدهم في جهدهم للعثور على الجمال في البرمجة.