كتابة محرك مغامرة نصية متعددة اللاعبين في Node.js: تصميم خادم محرك اللعبة (الجزء 2)
نشرت: 2022-03-10بعد بعض الدراسة المتأنية والتنفيذ الفعلي للوحدة ، كان لابد من تغيير بعض التعريفات التي قمت بها أثناء مرحلة التصميم. يجب أن يكون هذا مشهدًا مألوفًا لأي شخص سبق له العمل مع عميل متحمس يحلم بمنتج مثالي ولكنه يحتاج إلى ضبط النفس من قبل فريق التطوير.
بمجرد تنفيذ الميزات واختبارها ، سيبدأ فريقك في ملاحظة أن بعض الخصائص قد تختلف عن الخطة الأصلية ، ولا بأس بذلك. ببساطة قم بالإعلام والضبط والمتابعة. لذا ، بدون مزيد من اللغط ، اسمح لي أولاً أن أشرح ما تغير عن الخطة الأصلية.
أجزاء أخرى من هذه السلسلة
- الجزء الأول: المقدمة
- الجزء 3: إنشاء عميل المحطة الطرفية
- الجزء 4: إضافة الدردشة إلى لعبتنا
ميكانيكا المعركة
ربما يكون هذا هو التغيير الأكبر عن الخطة الأصلية. أعلم أنني قلت إنني سأذهب مع تنفيذ D & D-esque حيث سيحصل كل جهاز كمبيوتر شخصي و NPC مشاركين على قيمة مبادرة وبعد ذلك ، سندير معركة قائمة على أساس الدور. لقد كانت فكرة جيدة ، ولكن تنفيذها على خدمة قائمة على REST معقد بعض الشيء حيث لا يمكنك بدء الاتصال من جانب الخادم ، ولا الحفاظ على الحالة بين المكالمات.
لذا بدلاً من ذلك ، سأستفيد من الآليات المبسطة لـ REST وأستخدمها لتبسيط آليات معركتنا. سيكون الإصدار المطبق قائمًا على اللاعب بدلاً من الحزب ، وسيسمح للاعبين بمهاجمة الشخصيات غير القابلة للعب (شخصيات غير لاعب). إذا نجح هجومهم ، فسيتم قتل الشخصيات غير القابلة للعب وإلا فسوف يهاجمون إما بإلحاق الضرر باللاعب أو قتله.
سيتم تحديد ما إذا كان الهجوم ناجحًا أو فشلًا حسب نوع السلاح المستخدم ونقاط الضعف التي قد تكون لدى NPC. لذلك ، إذا كان الوحش الذي تحاول قتله ضعيفًا ضد سلاحك ، فإنه يموت. خلاف ذلك ، لن يتأثر - وعلى الأرجح - سيكون غاضبًا جدًا.
محفزات
إذا كنت قد أولت اهتمامًا وثيقًا لتعريف لعبة JSON من مقالتي السابقة ، فربما تكون قد لاحظت تعريف المشغل الموجود في عناصر المشهد. واحد خاص ينطوي على تحديث حالة اللعبة ( statusUpdate
). أثناء التنفيذ ، أدركت أن العمل كعنصر تبديل يوفر حرية محدودة. كما ترى ، بالطريقة التي تم تنفيذها (من وجهة نظر اصطلاحية) ، كنت قادرًا على تعيين حالة ولكن عدم ضبطها لم يكن خيارًا. لذا بدلاً من ذلك ، قمت باستبدال تأثير المشغل هذا بآخرتين جديدتين: addStatus
و removeStatus
. سيسمح لك ذلك بتحديد متى يمكن أن تحدث هذه التأثيرات بالضبط - إن وجدت. أشعر أن هذا أسهل كثيرًا في الفهم والعقل.
هذا يعني أن المشغلات تبدو الآن كما يلي:
"triggers": [ { "action": "pickup", "effect":{ "addStatus": "has light", "target": "game" } }, { "action": "drop", "effect": { "removeStatus": "has light", "target": "game" } } ]
عند التقاط العنصر ، نقوم بإعداد الحالة ، وعند إسقاطه ، نقوم بإزالته. بهذه الطريقة ، فإن وجود مؤشرات حالة متعددة على مستوى اللعبة أمر ممكن تمامًا ويسهل إدارته.
التطبيق
بعد هذه التحديثات ، يمكننا البدء في تغطية التنفيذ الفعلي. من وجهة نظر معمارية ، لم يتغير شيء ؛ ما زلنا نبني واجهة برمجة تطبيقات REST تحتوي على منطق محرك اللعبة الرئيسي.
المكدس التكنولوجي
بالنسبة لهذا المشروع بالذات ، فإن الوحدات التي سأستخدمها هي كما يلي:
وحدة | وصف |
---|---|
Express.js | من الواضح أنني سأستخدم Express ليكون أساس المحرك بأكمله. |
وينستون | كل شيء يتعلق بالتسجيل سيتولى وينستون. |
التكوين | سيتم التعامل مع كل متغير ثابت ومعتمد على البيئة بواسطة وحدة config.js ، مما يبسط إلى حد كبير مهمة الوصول إليها. |
النمس | سيكون هذا لدينا ORM. سأقوم بنمذجة جميع الموارد باستخدام نماذج النمس واستخدامها للتفاعل مباشرة مع قاعدة البيانات. |
uuid | سنحتاج إلى إنشاء بعض المعرفات الفريدة - ستساعدنا هذه الوحدة في هذه المهمة. |
بالنسبة للتقنيات الأخرى المستخدمة بخلاف Node.js ، لدينا MongoDB و Redis . أحب استخدام Mongo بسبب عدم وجود مخطط مطلوب. تسمح لي هذه الحقيقة البسيطة بالتفكير في الكود الخاص بي وتنسيقات البيانات ، دون الحاجة إلى القلق بشأن تحديث بنية الجداول أو عمليات ترحيل المخطط أو أنواع البيانات المتضاربة.
فيما يتعلق بـ Redis ، أميل إلى استخدامه كنظام دعم بقدر ما أستطيع في مشاريعي وهذه الحالة لا تختلف. سأستخدم Redis في كل ما يمكن اعتباره معلومات متقلبة ، مثل أرقام أعضاء الحزب ، وطلبات الأوامر ، وأنواع أخرى من البيانات صغيرة بما يكفي ومتقلبة بدرجة كافية بحيث لا تستحق التخزين الدائم.
سأستخدم أيضًا ميزة انتهاء الصلاحية الرئيسية في Redis لإدارة بعض جوانب التدفق تلقائيًا (المزيد حول هذا قريبًا).
تعريف API
قبل الانتقال إلى التفاعل بين الخادم والعميل وتعريفات تدفق البيانات ، أريد أن أتجاوز نقاط النهاية المحددة لواجهة برمجة التطبيقات هذه. إنها ليست كثيرة ، نحتاج في الغالب إلى الامتثال للسمات الرئيسية الموضحة في الجزء الأول:
ميزة | وصف |
---|---|
انضم إلى لعبة | سيتمكن اللاعب من الانضمام إلى اللعبة عن طريق تحديد معرف اللعبة. |
قم بإنشاء لعبة جديدة | يمكن للاعب أيضًا إنشاء مثيل لعبة جديد. يجب أن يقوم المحرك بإرجاع معرف ، حتى يتمكن الآخرون من استخدامه للانضمام. |
مشهد العودة | يجب أن تعيد هذه الميزة المشهد الحالي حيث توجد المجموعة. في الأساس ، سيعيد الوصف ، مع جميع المعلومات المرتبطة به (الإجراءات الممكنة ، والأشياء الموجودة فيه ، وما إلى ذلك). |
التفاعل مع المشهد | ستكون هذه واحدة من أكثر الأشياء تعقيدًا ، لأنها ستتخذ أمرًا من العميل وتقوم بهذا الإجراء - أشياء مثل التحرك ، والدفع ، والأخذ ، والنظر ، والقراءة ، على سبيل المثال لا الحصر. |
تأكد من المخزون | على الرغم من أن هذه طريقة للتفاعل مع اللعبة ، إلا أنها لا تتعلق مباشرة بالمشهد. لذا ، فإن فحص المخزون لكل لاعب سيعتبر إجراءً مختلفًا. |
تسجيل تطبيق العميل | تتطلب الإجراءات المذكورة أعلاه وجود عميل صالح لتنفيذها. ستقوم نقطة النهاية هذه بالتحقق من تطبيق العميل وإرجاع معرف العميل الذي سيتم استخدامه لأغراض المصادقة في الطلبات اللاحقة. |
القائمة أعلاه تترجم إلى قائمة نقاط النهاية التالية:
الفعل | نقطة النهاية | وصف |
---|---|---|
بريد | /clients | ستتطلب تطبيقات العميل الحصول على مفتاح معرف العميل باستخدام نقطة النهاية هذه. |
بريد | /games | يتم إنشاء مثيلات اللعبة الجديدة باستخدام نقطة النهاية هذه بواسطة تطبيقات العميل. |
بريد | /games/:id | بمجرد إنشاء اللعبة ، ستمكّن نقطة النهاية أعضاء المجموعة من الانضمام إليها وبدء اللعب. |
احصل على | /games/:id/:playername | ستعيد نقطة النهاية هذه حالة اللعبة الحالية للاعب معين. |
بريد | /games/:id/:playername/commands | أخيرًا ، باستخدام نقطة النهاية هذه ، سيتمكن تطبيق العميل من إرسال الأوامر (بمعنى آخر ، سيتم استخدام نقطة النهاية هذه للعب). |
دعني أخوض في مزيد من التفاصيل حول بعض المفاهيم التي وصفتها في القائمة السابقة.
تطبيقات العميل
ستحتاج تطبيقات العميل إلى التسجيل في النظام لبدء استخدامه. جميع نقاط النهاية (باستثناء النقطة الأولى في القائمة) مؤمنة وستتطلب مفتاح تطبيق صالح لإرساله مع الطلب. من أجل الحصول على هذا المفتاح ، تحتاج تطبيقات العميل إلى طلب واحد فقط. بمجرد تقديمها ، ستستمر طوال فترة استخدامها ، أو ستنتهي صلاحيتها بعد شهر من عدم استخدامها. يتم التحكم في هذا السلوك من خلال تخزين المفتاح في Redis وتعيين TTL لمدة شهر واحد.
مثيل اللعبة
يعني إنشاء لعبة جديدة بشكل أساسي إنشاء نسخة جديدة من لعبة معينة. سيحتوي هذا الإصدار الجديد على نسخة من جميع المشاهد ومحتواها. أي تعديلات يتم إجراؤها على اللعبة ستؤثر فقط على المجموعة. بهذه الطريقة ، يمكن للعديد من المجموعات أن تلعب نفس اللعبة بطريقتها الفردية.
حالة لعبة اللاعب
هذا مشابه للسابق ، لكنه فريد لكل لاعب. بينما يحتفظ مثيل اللعبة بحالة اللعبة للطرف بأكمله ، فإن حالة لعبة اللاعب تحتفظ بالوضع الحالي للاعب واحد معين. بشكل أساسي ، يحتفظ هذا بالمخزون والموقع والمشهد الحالي و HP (نقاط الصحة).
أوامر اللاعب
بمجرد إعداد كل شيء وتسجيل تطبيق العميل والانضمام إلى لعبة ، يمكنه البدء في إرسال الأوامر. تتضمن الأوامر المنفذة في هذا الإصدار من المحرك: move
، look
، pickup
، attack
.
- سيسمح لك أمر
move
باجتياز الخريطة. ستتمكن من تحديد الاتجاه الذي تريد التحرك نحوه وسيخبرك المحرك بالنتيجة. إذا ألقيت نظرة سريعة على الجزء 1 ، يمكنك رؤية الطريقة التي اتبعتها للتعامل مع الخرائط. (باختصار ، يتم تمثيل الخريطة كرسم بياني ، حيث تمثل كل عقدة غرفة أو مشهدًا وتكون متصلة فقط بالعقد الأخرى التي تمثل الغرف المجاورة.)
المسافة بين العقد موجودة أيضًا في التمثيل ومقترنة بالسرعة القياسية للاعب ؛ قد لا يكون الانتقال من غرفة إلى أخرى بسيطًا مثل ذكر الأمر ، ولكن سيتعين عليك أيضًا اجتياز المسافة. في الممارسة العملية ، هذا يعني أن الانتقال من غرفة إلى أخرى قد يتطلب عدة أوامر نقل). الجانب الآخر المثير للاهتمام لهذا الأمر يأتي من حقيقة أن هذا المحرك يهدف إلى دعم أطراف متعددة اللاعبين ، ولا يمكن تقسيم الحزب (على الأقل ليس في الوقت الحالي).
لذلك ، فإن الحل لهذا يشبه نظام التصويت: سيرسل كل عضو في الحزب طلب أمر نقل وقتما يريدون. بمجرد قيام أكثر من نصفهم بذلك ، سيتم استخدام الاتجاه الأكثر طلبًا. -
look
مختلفة تمامًا عن الحركة. يسمح للاعب بتحديد اتجاه أو عنصر أو NPC الذي يريدون فحصه. المنطق الرئيسي وراء هذا الأمر ، يؤخذ في الاعتبار عندما تفكر في الأوصاف المعتمدة على الحالة.
على سبيل المثال ، لنفترض أنك دخلت غرفة جديدة ، لكنها مظلمة تمامًا (لا ترى أي شيء) ، وأنت تتقدم للأمام بينما تتجاهلها. بعد بضع غرف ، تلتقط مصباحًا مضاءًا من الحائط. لذا يمكنك الآن العودة وإعادة فحص تلك الغرفة المظلمة. نظرًا لأنك التقطت الشعلة ، يمكنك الآن رؤية ما بداخلها ، وتكون قادرًا على التفاعل مع أي من العناصر والشخصيات غير القابلة للعب التي تجدها هناك.
يتم تحقيق ذلك من خلال الحفاظ على مجموعة من سمات الحالة على مستوى اللعبة واللاعبين والسماح لمنشئ اللعبة بتحديد العديد من الأوصاف لعناصرنا المعتمدة على الحالة في ملف JSON. يتم بعد ذلك تجهيز كل وصف بنص افتراضي ومجموعة من النصوص الشرطية ، اعتمادًا على الحالة الحالية. هذه الأخيرة اختيارية ؛ الشيء الوحيد الإلزامي هو القيمة الافتراضية.
بالإضافة إلى ذلك ، يحتوي هذا الأمر على نسخة مختصرةlook at room: look around
؛ ذلك لأن اللاعبين سيحاولون تفتيش غرفة في كثير من الأحيان ، لذا فإن تقديم أمر مختصرة (أو اسم مستعار) يسهل كتابته أمر منطقي للغاية. - يلعب أمر
pickup
دورًا مهمًا جدًا في طريقة اللعب. يعتني هذا الأمر بإضافة العناصر إلى مخزون اللاعبين أو أيديهم (إذا كانت مجانية). من أجل فهم المكان الذي من المفترض أن يتم تخزين كل عنصر فيه ، فإن تعريفهم له خاصية "الوجهة" التي تحدد ما إذا كان مخصصًا للمخزون أو أيدي اللاعب. يتم بعد ذلك إزالة أي شيء يتم التقاطه بنجاح من المشهد منه ، وتحديث نسخة اللعبة من اللعبة. - سيسمح لك الأمر
use
بالتأثير على البيئة باستخدام عناصر في مخزونك. على سبيل المثال ، سيسمح لك التقاط مفتاح في غرفة باستخدامه لفتح باب مغلق في غرفة أخرى. - هناك أمر خاص ، أمر لا يتعلق بلعبة اللعب ، ولكن بدلاً من ذلك ، هناك أمر مساعد يهدف إلى الحصول على معلومات معينة ، مثل معرف اللعبة الحالي أو اسم اللاعب. يسمى هذا الأمر get ، ويمكن للاعبين استخدامه للاستعلام عن محرك اللعبة. على سبيل المثال: احصل على gameid .
- أخيرًا ، آخر أمر تم تنفيذه لهذا الإصدار من المحرك هو أمر
attack
. لقد غطيت هذا بالفعل ؛ في الأساس ، سيتعين عليك تحديد هدفك والسلاح الذي تهاجمه به. بهذه الطريقة سيتمكن النظام من التحقق من نقاط ضعف الهدف وتحديد ناتج هجومك.
التفاعل بين العميل والمحرك
من أجل فهم كيفية استخدام نقاط النهاية المذكورة أعلاه ، دعني أوضح لك كيف يمكن لأي عميل محتمل أن يتفاعل مع واجهة برمجة التطبيقات الجديدة الخاصة بنا.
خطوة | وصف |
---|---|
تسجيل العميل | أول الأشياء أولاً ، يحتاج تطبيق العميل إلى طلب مفتاح API ليتمكن من الوصول إلى جميع نقاط النهاية الأخرى. للحصول على هذا المفتاح ، يجب التسجيل في منصتنا. المعلمة الوحيدة التي يجب تقديمها هي اسم التطبيق ، هذا كل شيء. |
ابتكر لعبة | بعد الحصول على مفتاح API ، فإن أول شيء يجب فعله (بافتراض أن هذا تفاعل جديد تمامًا) هو إنشاء مثيل لعبة جديد تمامًا. فكر في الأمر بهذه الطريقة: يحتوي ملف JSON الذي قمت بإنشائه في آخر مشاركة لي على تعريف اللعبة ، لكننا نحتاج إلى إنشاء مثيل له فقط لك ولحزبك (فصول التفكير والعناصر ، نفس الصفقة). يمكنك أن تفعل بهذا المثال ما تريد ، ولن يؤثر على الأطراف الأخرى. |
الانضمام إلى اللعبة | بعد إنشاء اللعبة ، ستحصل على معرف اللعبة مرة أخرى من المحرك. يمكنك بعد ذلك استخدام معرف اللعبة هذا للانضمام إلى المثيل باستخدام اسم المستخدم الفريد الخاص بك. ما لم تنضم إلى اللعبة ، لا يمكنك اللعب ، لأن الانضمام إلى اللعبة سيخلق أيضًا حالة لعبة لك وحدك. سيكون هذا هو المكان الذي يتم فيه حفظ مخزونك ومركزك وإحصائياتك الأساسية فيما يتعلق باللعبة التي تلعبها. من المحتمل أن تلعب عدة ألعاب في نفس الوقت ، وفي كل منها دول مستقلة. |
أرسل الأوامر | بمعنى آخر: العب اللعبة. الخطوة الأخيرة هي بدء إرسال الأوامر. تمت تغطية كمية الأوامر المتاحة بالفعل ، ويمكن تمديدها بسهولة (المزيد حول هذا بعد قليل). في كل مرة ترسل فيها أمرًا ، ستعيد اللعبة حالة اللعبة الجديدة لعميلك لتحديث وجهة نظرك وفقًا لذلك. |
دعونا نتسخ أيدينا
لقد انتهيت من التصميم بقدر ما أستطيع ، على أمل أن تساعدك هذه المعلومات على فهم الجزء التالي ، لذلك دعونا ندخل في تفاصيل محرك اللعبة.
ملحوظة : لن أعرض لك الكود الكامل في هذه المقالة لأنه كبير جدًا وليس كله مثيرًا للاهتمام. بدلاً من ذلك ، سأعرض الأجزاء الأكثر صلة وأرتبط بالمستودع الكامل في حال كنت تريد المزيد من التفاصيل.
الملف الرئيسي
أول الأشياء أولاً: هذا مشروع Express وقد تم إنشاء رمز معياري قائم عليه باستخدام المولد الخاص بـ Express ، لذلك يجب أن يكون ملف app.js مألوفًا لك. أريد فقط إجراء تعديلين أود القيام بهما على هذا الرمز لتبسيط عملي.
أولاً ، أقوم بإضافة المقتطف التالي لأتمتة تضمين ملفات التوجيه الجديدة:
const requireDir = require("require-dir") const routes = requireDir("./routes") //... Object.keys(routes).forEach( (file) => { let cnt = routes[file] app.use('/' + file, cnt) })
إنه أمر بسيط حقًا ، لكنه يلغي الحاجة إلى طلب كل ملفات مسار تقوم بإنشائها يدويًا في المستقبل. بالمناسبة ، تعد require-dir
وحدة نمطية بسيطة تهتم تلقائيًا بطلب كل ملف داخل مجلد. هذا هو.
التغيير الآخر الذي أحب القيام به هو تعديل معالج الأخطاء الخاص بي قليلاً. يجب أن أبدأ حقًا في استخدام شيء أكثر قوة ، ولكن بالنسبة للاحتياجات في متناول اليد ، أشعر أن هذا ينجز العمل:
// error handler app.use(function(err, req, res, next) { // render the error page if(typeof err === "string") { err = { status: 500, message: err } } res.status(err.status || 500); let errorObj = { error: true, msg: err.message, errCode: err.status || 500 } if(err.trace) { errorObj.trace = err.trace } res.json(errorObj); });
يعتني الكود أعلاه بالأنواع المختلفة من رسائل الخطأ التي قد يتعين علينا التعامل معها - إما كائنات كاملة أو كائنات خطأ فعلية تم إلقاؤها بواسطة جافا سكريبت أو رسائل خطأ بسيطة بدون أي سياق آخر. هذا الرمز سيأخذ كل شيء وينسقه في تنسيق قياسي.
التعامل مع الأوامر
هذا هو جانب آخر من تلك الجوانب من المحرك التي يجب أن تكون سهلة التمديد. في مشروع مثل هذا ، من المنطقي تمامًا افتراض ظهور أوامر جديدة في المستقبل. إذا كان هناك شيء تريد تجنبه ، فمن المحتمل أن يتم تجنب إجراء تغييرات على الكود الأساسي عند محاولة إضافة شيء جديد لمدة ثلاثة أو أربعة أشهر في المستقبل.
لن يجعل أي قدر من تعليقات الكود مهمة تعديل الكود الذي لم تلمسه (أو حتى فكرت فيه) أمرًا سهلاً خلال عدة أشهر ، لذا فإن الأولوية هي تجنب أكبر عدد ممكن من التغييرات. لحسن حظنا ، هناك بعض الأنماط التي يمكننا تنفيذها لحل هذه المشكلة. على وجه الخصوص ، استخدمت مزيجًا من أنماط الأوامر والمصنع.
لقد قمت بشكل أساسي بتغليف سلوك كل أمر داخل فئة واحدة ترث من فئة BaseCommand
التي تحتوي على الكود العام لجميع الأوامر. في الوقت نفسه ، أضفت وحدة CommandParser
التي تلتقط السلسلة التي أرسلها العميل وتعيد الأمر الفعلي لتنفيذه.
المحلل اللغوي بسيط جدًا نظرًا لأن جميع الأوامر المنفذة لديها الآن الأمر الفعلي لكلمتهم الأولى (على سبيل المثال "التحرك شمالًا" ، "التقاط السكين" ، وما إلى ذلك) إنها مسألة بسيطة لتقسيم السلسلة والحصول على الجزء الأول:
const requireDir = require("require-dir") const validCommands = requireDir('./commands') class CommandParser { constructor(command) { this.command = command } normalizeAction(strAct) { strAct = strAct.toLowerCase().split(" ")[0] return strAct } verifyCommand() { if(!this.command) return false if(!this.command.action) return false if(!this.command.context) return false let action = this.normalizeAction(this.command.action) if(validCommands[action]) { return validCommands[action] } return false } parse() { let validCommand = this.verifyCommand() if(validCommand) { let cmdObj = new validCommand(this.command) return cmdObj } else { return false } } }
ملاحظة : أنا أستخدم الوحدة النمطية require-dir
مرة أخرى لتبسيط إدراج أي فئات أوامر حالية وجديدة. أقوم ببساطة بإضافته إلى المجلد ويمكن للنظام بأكمله استلامه واستخدامه.
مع ذلك ، هناك العديد من الطرق التي يمكن من خلالها تحسين ذلك ؛ على سبيل المثال ، من خلال القدرة على إضافة دعم مرادف لأوامرنا ستكون ميزة رائعة (لذا فإن قول "تحرك شمالًا" أو "اتجه شمالًا" أو حتى "مشي شمالًا" يعني نفس الشيء). هذا شيء يمكننا التركيز عليه في هذه الفئة والتأثير على جميع الأوامر في نفس الوقت.
لن أخوض في التفاصيل حول أي من الأوامر لأنه ، مرة أخرى ، هذا رمز كثير جدًا لعرضه هنا ، ولكن يمكنك أن ترى في رمز المسار التالي كيف تمكنت من تعميم هذا التعامل مع الأوامر الحالية (وأي مستقبلية):
/** Interaction with a particular scene */ router.post('/:id/:playername/:scene', function(req, res, next) { let command = req.body command.context = { gameId: req.params.id, playername: req.params.playername, } let parser = new CommandParser(command) let commandObj = parser.parse() //return the command instance if(!commandObj) return next({ //error handling status: 400, errorCode: config.get("errorCodes.invalidCommand"), message: "Unknown command" }) commandObj.run((err, result) => { //execute the command if(err) return next(err) res.json(result) }) })
تتطلب جميع الأوامر فقط طريقة run
- أي شيء آخر يكون إضافيًا ومخصصًا للاستخدام الداخلي.
أنا أشجعك على الذهاب ومراجعة كود المصدر بالكامل (حتى يمكنك تنزيله واللعب به إذا أردت!). في الجزء التالي من هذه السلسلة ، سأوضح لك التنفيذ الفعلي للعميل وتفاعل واجهة برمجة التطبيقات هذه.
خواطر ختامية
ربما لم أقم بتغطية الكثير من التعليمات البرمجية الخاصة بي هنا ، لكني ما زلت آمل أن تكون المقالة مفيدة لتوضيح كيفية تعاملي مع المشاريع - حتى بعد مرحلة التصميم الأولية. أشعر أن الكثير من الأشخاص يحاولون بدء البرمجة كأول استجابة لفكرة جديدة ويمكن أن ينتهي الأمر في بعض الأحيان بإحباط أحد المطورين نظرًا لعدم وجود خطة حقيقية أو أي أهداف لتحقيقها - بخلاف وجود المنتج النهائي جاهزًا ( وهذا يمثل حدثًا كبيرًا جدًا بحيث يتعذر معالجته من اليوم الأول). مرة أخرى ، آمل أن أشارك هذه المقالات بطريقة مختلفة للعمل بمفردك (أو كجزء من مجموعة صغيرة) في المشاريع الكبيرة.
أتمنى أن تكون قد استمتعت بالقراءة! لا تتردد في ترك تعليق أدناه مع أي نوع من الاقتراحات أو التوصيات ، فأنا أحب قراءة ما تعتقده وإذا كنت حريصًا على بدء اختبار API باستخدام رمز جانب العميل الخاص بك.
نراكم في المرحلة التالية!
أجزاء أخرى من هذه السلسلة
- الجزء الأول: المقدمة
- الجزء 3: إنشاء عميل المحطة الطرفية
- الجزء 4: إضافة الدردشة إلى لعبتنا