מערכת דמויות במשחק
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 107 מתוך 107.
אתגר
קלבואו נבנה מערכת דמויות למשחק שמשלבת הטמעת struct, ממשקים ותבנית Strategy ליצירת מכניקות לחימה גמישות! תתכננו סוגי דמויות שונים שיכולים לתקוף ולהגן בדרכים ייחודיות, עם יכולות שאפשר להחליף בזמן הריצה.
תארגנו את הקוד בחמישה קבצים:
character.go: הגדירו struct בסיסי בשםCharacterעם שדות משותפים:Name(string),Health(int) ו-MaxHealth(int). כללו בנאיNewCharacterואת המתודות:IsAlive() bool: מחזירה true אם הבריאות גדולה מ-0Status() string: מחזירה את הפורמט:[Name]: [Health]/[MaxHealth] HP
interfaces.go: הגדירו את ממשקי הלחימה שמאפשרים פולימורפיזם:- הממשק
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: ממשו את תבנית Strategy עם יכולות שניתן להחליף:struct בשם
FireSpellעםPower(int). המתודהUse()שלו מחזירה:Casts fireball for [Power] damagestruct בשם
IceSpellעםPower(int). המתודהUse()שלו מחזירה:Casts ice shard for [Power] damagestruct בשם
PowerStrikeעםMultiplier(int). המתודהUse()שלו מחזירה:Performs power strike at [Multiplier]x damageהוסיפו למבנה Mage שדה
abilityמסוגAbility, יחד עם המתודותSetAbility(a Ability)ו-UseAbility() string. אם ability הוא nil, UseAbility מחזירהNo ability set.main.go: צרו דמויות והדגימו את מכניקות הלחימה.קראו את סוג הדמות (
warrior,mageאוarcher), ואז קראו את שם הדמות, את הבריאות ואת המאפיינים הספציפיים לסוג. צרו את הדמות המתאימה.לאחר מכן קראו את מספר הפעולות. כל פעולה היא אחת מהבאות:
status: הדפיסו את מצב הדמותattack: הדפיסו[Name] attacks for [damage] damagedefend [amount]: החילו נזק והדפיסו[Name] takes [actual] damagesetability [type] [power]: עבור קוסמים בלבד, הגדירו יכולת (הסוג הואfire,iceאוstrike)useability: עבור קוסמים בלבד, הדפיסו את התוצאה של 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 מאפשרת לכל סוגי הדמויות לשתף את השדות והמתודות של Character הבסיסי, בעוד שממשקים מאפשרים לחימה פולימורפית שבה כל Attacker יכול לגרום נזק וכל Defender יכול לספוג אותו. תבנית Strategy באה לידי ביטוי ביכולות הניתנות להחלפה של 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())
// קרא את ערך הבריאות
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
// עבור לוחם: stat1 = Strength, stat2 = Armor
// עבור קוסם: stat1 = Intelligence, stat2 = MagicResist
// עבור קשת: 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]": עבור קוסמים, הגדירו יכולת (fire, ice או strike)
// - "useability": עבור קוסמים, הדפיסו את התוצאה של UseAbility()
_ = action // הסירו את השורה הזו בעת המימוש
_ = fmt.Println // הסירו את השורה הזו בעת המימוש
}
}
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות OOP ב-Go
קבצים חיצונייםסביבת העבודה והמודולים של Goחבילות וייבואשמות מיוצאים לעומת שמות לא מיוצאיםמבוא ל-OOP ב-GoStructs כמחלקותהגדרת מתודות עבור Structsמקבלי Pointer לעומת Valueאתחול Structפונקציות Constructorחזרה – מחשבון פשוט4ממשקים
מבוא לממשקיםמימוש משתמעממשק כחוזהממשק ריק (any)אימות טיפוסמתג טיפוסיםהרכבת ממשקיםממשקי Stringer ו-Errorסיכום – מחשבון צורות7כימוס
שדות מיוצאים לעומת שדות לא מיוצאיםכימוס ברמת החבילהמתודות Getter ו-Setterהסתרת מידע ב-Goסיכום – רשומות תלמידים10Generics (Go 1.18 ומעלה)
מבוא ל־Genericsפרמטרים של טיפוסיםאילוצי טיפוסיםמבנים גנרייםפתרון עוקף למתודות גנריותסיכום – אוסף גנרי2העמקה בסוגים וב-Structs
סוגים בסיסיים ומורכביםהגדרות של סוגים מותאמים אישיתתגיות StructStructs אנונימייםStructs מקונניםערכי אפס וברירות מחדלסיכום – ספר אנשי קשר5קומפוזיציה במקום ירושה
למה ב-Go אין ירושהיסודות הטמעת Structקידום מתודותהטמעת כמה Structsהטמעה לעומת אגרגציההסתרת מתודות מוטמעותסיכום – היררכיית עובדים8טיפול בשגיאות ו-OOP
ממשק errorסוגי שגיאות מותאמים אישיתעטיפת שגיאות (fmt.Errorf)שגיאות Sentinelerrors.Is() ו-errors.As()Panic, Defer ו-Recoverסיכום – מנתח קבצים11ספרייה סטנדרטית ותכנות מונחה עצמים
io.Reader ו-io.Writersort.Interfaceהממשק fmt.Stringerencoding/json עם מבניםהממשק http.Handlerסיכום – מודלים של REST API3מצביעים וזיכרון
יסודות המצביעים ב-Goמצביעים למבניםהעברה לפי ערך לעומת לפי הפניההפונקציה new()איסוף אשפה ב-Goסיכום - בונה רשימה מקושרת6פולימורפיזם ב-Go
פולימורפיזם באמצעות ממשקיםטיפוס ברווז ב-Goכללי התאמה לממשקאוספים פולימורפייםהזרקת תלויותסיכום - מעבד תשלומים9מקביליות ו-OOP
יסודות GoroutinesChannels ותקשורתChannel עם חוצץ לעומת ללא חוצץפקודת Selectsync.Mutex ו-sync.RWMutexsync.WaitGroupתכנון Struct בטוח לשימוש במקבילסיכום - מאגר עובדים12מושגים מתקדמים בתכנות מונחה עצמים
דפוס האפשרויות הפונקציונליותדפוס Builder ב-Goשרשור מתודותכינויי טיפוסים לעומת הגדרותיסודות Reflectionסקירה כללית של יצירת קודתרגלו בעצמכם: קומפיילר Go אונליין