نظام شخصيات الألعاب
جزء من قسم Object Oriented Programming في رحلة GO على Coddy — الدرس 107 من 107.
التحدي
سهللنقم ببناء نظام شخصيات ألعاب يجمع بين تضمين الهياكل (struct embedding)، والواجهات (interfaces)، ونمط الاستراتيجية (Strategy pattern) لإنشاء آليات قتال مرنة! ستصمم أنواعًا مختلفة من الشخصيات التي يمكنها الهجوم والدفاع بطرق فريدة، مع قدرات يمكن تبديلها أثناء وقت التشغيل.
ستقوم بتنظيم الكود الخاص بك عبر خمسة ملفات:
character.go: قم بتعريف هيكل أساسي باسمCharacterمع حقول مشتركة:Name(string)، وHealth(int)، وMaxHealth(int). قم بتضمين منشئNewCharacterوالدوال التالية:IsAlive() bool— ترجع true إذا كانت الصحة أكبر من 0Status() string— ترجع التنسيق التالي:[Name]: [Health]/[MaxHealth] HP
interfaces.go: قم بتعريف واجهات القتال التي تسمح بتعدد الأشكال (polymorphism):- واجهة
Attackerمع دالةAttack() int— ترجع الضرر الملحق - واجهة
Defenderمع دالةTakeDamage(damage int) int— تطبق تقليل الضرر وترجع الضرر الفعلي المتلقى - واجهة
Abilityمع دالةUse() string— ترجع وصفًا لتأثير القدرة
- واجهة
characters.go: قم بإنشاء ثلاثة أنواع من الشخصيات التي تتضمنCharacterوتنفذ كلاً منAttackerوDefender:Warrior— يحتوي على حقولStrength(int) وArmor(int). ترجع دالة Attack قيمة Strength كضرر. تقوم دالة TakeDamage بتقليل الضرر الوارد بمقدار Armor (بحد أدنى 1 من الضرر المتلقى).Mage— يحتوي على حقولIntelligence(int) وMagicResist(int). ترجع دالة Attack قيمة Intelligence * 2 كضرر. تقوم دالة TakeDamage بتقليل الضرر الوارد بمقدار MagicResist / 2 (بحد أدنى 1 من الضرر المتلقى).Archer— يحتوي على حقولAgility(int) وEvasion(int). ترجع دالة Attack قيمة Agility + 5 كضرر. دالة TakeDamage: إذا كانت Evasion > 10، يتم تقليل الضرر بنسبة 25% (قسمة عدد صحيح)، وإلا يتم تلقي الضرر كاملاً (بحد أدنى 1 من الضرر المتلقى).قم بتضمين منشئات لكل منها:
NewWarrior،NewMage،NewArcherالتي تأخذ الاسم، والصحة، وقيم الإحصائيات الخاصة بها.abilities.go: قم بتنفيذ نمط الاستراتيجية مع قدرات قابلة للتبديل:هيكل
FireSpellمعPower(int). دالةUse()الخاصة به ترجع:Casts fireball for [Power] damageهيكل
IceSpellمعPower(int). دالةUse()الخاصة به ترجع:Casts ice shard for [Power] damageهيكل
PowerStrikeمعMultiplier(int). دالةUse()الخاصة به ترجع:Performs power strike at [Multiplier]x damageأضف حقل
ability(من نوعAbility) إلى هيكل Mage، مع دوالSetAbility(a Ability)وUseAbility() string. ترجع UseAbility القيمةNo ability setإذا كانت القدرة nil.main.go: قم بإنشاء الشخصيات واستعراض آليات القتال.اقرأ نوع الشخصية (
warrior، أوmage، أوarcher)، ثم اقرأ اسم الشخصية، وصحتها، والإحصائيات الخاصة بنوعها. قم بإنشاء الشخصية المناسبة.ثم اقرأ عدد الإجراءات. كل إجراء هو واحد مما يلي:
status— طباعة حالة الشخصيةattack— طباعة[Name] attacks for [damage] damagedefend [amount]— تطبيق الضرر وطباعة[Name] takes [actual] damagesetability [type] [power]— للسحرة (mages) فقط، تعيين القدرة (النوع هوfire، أوice، أوstrike)useability— للسحرة (mages) فقط، طباعة نتيجة Use() للقدرة
سيتم توفير المدخلات التالية:
- نوع الشخصية، والاسم، والصحة، والإحصائيات الخاصة بالنوع
- عدد الإجراءات، ثم كل إجراء في سطر منفصل
على سبيل المثال، إذا كان المدخل:
warrior
Thorin
100
15
8
5
status
attack
defend 20
status
defend 100يجب أن يكون مخرجك:
Thorin: 100/100 HP
Thorin attacks for 15 damage
Thorin takes 12 damage
Thorin: 88/100 HP
Thorin takes 92 damageوإذا كان المدخل:
mage
Gandalf
80
25
10
6
attack
useability
setability fire 30
useability
setability ice 25
useability
defend 40يجب أن يكون مخرجك:
Gandalf attacks for 50 damage
No ability set
Casts fireball for 30 damage
Casts ice shard for 25 damage
Gandalf takes 35 damageوإذا كان المدخل:
archer
Legolas
90
18
15
4
status
attack
defend 24
statusيجب أن يكون مخرجك:
Legolas: 90/90 HP
Legolas attacks for 23 damage
Legolas takes 18 damage
Legolas: 72/90 HPلاحظ كيف يسمح تضمين الهياكل (struct embedding) لكل نوع شخصية بمشاركة حقول ودوال Character الأساسية، بينما تتيح الواجهات قتالاً متعدد الأشكال حيث يمكن لأي Attacker إلحاق الضرر ولأي Defender تلقيه. يبرز نمط الاستراتيجية مع قدرات Mage القابلة للتبديل — يمكن لنفس الشخصية استخدام تعاويذ مختلفة دون تغيير تنفيذها الأساسي!
جرّب بنفسك
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// قراءة نوع الشخصية
scanner.Scan()
charType := strings.TrimSpace(scanner.Text())
// قراءة اسم الشخصية
scanner.Scan()
name := strings.TrimSpace(scanner.Text())
// قراءة الصحة (health)
scanner.Scan()
health, _ := strconv.Atoi(strings.TrimSpace(scanner.Text()))
// قراءة الإحصائية الأولى الخاصة بالنوع
scanner.Scan()
stat1, _ := strconv.Atoi(strings.TrimSpace(scanner.Text()))
// قراءة الإحصائية الثانية الخاصة بالنوع
scanner.Scan()
stat2, _ := strconv.Atoi(strings.TrimSpace(scanner.Text()))
// TODO: إنشاء الشخصية المناسبة بناءً على charType
// استخدام المتغيرات: charType، name، health، stat1، stat2
// بالنسبة لـ warrior: stat1 = Strength، stat2 = Armor
// بالنسبة لـ mage: stat1 = Intelligence، stat2 = MagicResist
// بالنسبة لـ archer: stat1 = Agility، stat2 = Evasion
// قراءة عدد الإجراءات
scanner.Scan()
numActions, _ := strconv.Atoi(strings.TrimSpace(scanner.Text()))
// معالجة كل إجراء
for i := 0; i < numActions; i++ {
scanner.Scan()
line := strings.TrimSpace(scanner.Text())
parts := strings.Fields(line)
action := parts[0]
// TODO: التعامل مع كل نوع من أنواع الإجراءات:
// - "status": طباعة Status() الخاصة بالشخصية
// - "attack": طباعة "[Name] attacks for [damage] damage"
// - "defend [amount]": تطبيق الضرر وطباعة "[Name] takes [actual] damage"
// - "setability [type] [power]": بالنسبة لـ mages، تعيين القدرة (fire، ice، أو strike)
// - "useability": بالنسبة لـ mages، طباعة نتيجة UseAbility()
_ = action // قم بإزالة هذا السطر عند التنفيذ
_ = fmt.Println // قم بإزالة هذا السطر عند التنفيذ
}
}
جميع دروس Object Oriented Programming
1أساسيات الـ OOP في Go
الملفات الخارجيةمساحة العمل والـ Modules في Goالـ Packages والـ Importsالأسماء المصدرة مقابل غير المصدرةمقدمة إلى الـ OOP في Goالـ Structs كـ Classesتعريف الـ Methods في الـ Structsالـ Pointer Receivers مقابل الـ Value Receiversتهيئة الـ Structدوال الـ Constructorمراجعة - آلة حاسبة بسيطة4الواجهات
مقدمة في الواجهاتالتنفيذ الضمنيالواجهة كعقدالواجهة الفارغة (any)تأكيد النوع (Type Assertion)تبديل النوع (Type Switch)تركيب الواجهاتواجهات Stringer و Errorمراجعة - حاسبة الأشكال7التغليف
الحقول المصدرة وغير المصدرةالتغليف على مستوى الحزمةدوال الـ Getter والـ Setterإخفاء المعلومات في Goملخص - سجلات الطلاب10الأنواع العامة (Go 1.18+)
مقدمة في الأنواع العامةمعاملات الأنواعقيود الأنواعالـ Structs العامةحل بديل للـ Methods العامةملخص - المجموعات العامة13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Abstract Factoryنمط Observerنمط Strategy2تعمق في الأنواع و Structs
الأنواع الأساسية والمركبةتعريفات الأنواع المخصصةStruct TagsStructs مجهولةStructs متداخلةالقيم الصفرية والافتراضيةمراجعة - دفتر العناوين5التركيب بدلاً من الوراثة
لماذا لا تدعم Go الوراثةأساسيات تضمين الـ Structترقية الـ Methodتضمين عدة Structsالتضمين مقابل التجميعحجب الـ Methods المضمنةملخص - الهيكل الهرمي للموظفين8معالجة الأخطاء و OOP
واجهة errorأنواع الأخطاء المخصصةتغليف الأخطاء (fmt.Errorf)أخطاء Sentinelerrors.Is() و errors.As()Panic و Defer و Recoverملخص - File Parser11المكتبة القياسية والبرمجة كائنية التوجه (OOP)
io.Reader و io.Writerواجهة sort.Interfaceواجهة fmt.Stringerencoding/json مع Structsواجهة http.Handlerمراجعة - نماذج REST API14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبMiddleware كنمط مزين3المؤشرات والذاكرة
أساسيات المؤشرات في Goالمؤشرات إلى الـ Structsالتمرير بالقيمة مقابل التمرير بالمرجعدالة ()newGarbage Collection في Goملخص - بناء Linked List6تعدد الأشكال في Go
تعدد الأشكال عبر InterfacesDuck Typing في Goقواعد استيفاء الـ Interfaceمجموعات متعددة الأشكالحقن التبعيةمراجعة - معالج الدفع9التزامن والبرمجة كائنية التوجه (OOP)
أساسيات الـ Goroutinesالقنوات (Channels) والاتصالالقنوات المخزنة (Buffered) مقابل غير المخزنةجملة Selectsync.Mutex و sync.RWMutexsync.WaitGroupتصميم الـ Structs الآمنة للخيوط (Thread-Safe)مراجعة - Worker Pool