Hệ thống nhân vật trong trò chơi
Thuộc phần Lập trình hướng đối tượng trong hành trình GO của Coddy. Bài học 107 trên 107.
Thử thách
DễHãy cùng xây dựng một hệ thống nhân vật trong trò chơi kết hợp nhúng struct, interface và mẫu Strategy để tạo ra cơ chế chiến đấu linh hoạt! Bạn sẽ thiết kế các kiểu nhân vật khác nhau, có thể tấn công và phòng thủ theo cách riêng, với các năng lực có thể thay đổi ngay khi chương trình đang chạy.
Bạn sẽ sắp xếp code trong năm tệp:
character.go: Định nghĩa structCharactercơ sở với các trường dùng chung:Name(string),Health(int) vàMaxHealth(int). Thêm hàm khởi tạoNewCharactervà các phương thức:IsAlive() bool: trả về true nếu máu lớn hơn 0Status() string: trả về theo định dạng:[Name]: [Health]/[MaxHealth] HP
interfaces.go: Định nghĩa các interface chiến đấu cho phép đa hình:- Interface
AttackervớiAttack() int: trả về lượng sát thương gây ra - Interface
DefendervớiTakeDamage(damage int) int: giảm sát thương và trả về lượng sát thương thực tế phải chịu - Interface
AbilityvớiUse() string: trả về mô tả hiệu ứng của năng lực
- Interface
characters.go: Tạo ba kiểu nhân vật nhúngCharactervà triển khai cảAttackerlẫnDefender:Warrior: có trườngStrength(int) vàArmor(int). Attack trả về Strength làm sát thương. TakeDamage giảm sát thương nhận vào theo Armor (lượng sát thương phải chịu tối thiểu là 1).Mage: có các trườngIntelligence(int) vàMagicResist(int). Attack trả về Intelligence * 2 làm sát thương. TakeDamage giảm sát thương nhận vào theo MagicResist / 2 (lượng sát thương phải chịu tối thiểu là 1).Archer: có các trườngAgility(int) vàEvasion(int). Attack trả về Agility + 5 làm sát thương. TakeDamage: nếu Evasion > 10, giảm sát thương đi 25% (chia số nguyên), nếu không thì chịu toàn bộ sát thương (lượng sát thương phải chịu tối thiểu là 1).Thêm hàm khởi tạo cho từng kiểu:
NewWarrior,NewMage,NewArcher, nhận tên, máu và các giá trị chỉ số riêng của từng kiểu.abilities.go: Triển khai mẫu Strategy với các năng lực có thể thay đổi:Struct
FireSpellvớiPower(int).Use()của struct này trả về:Casts fireball for [Power] damageStruct
IceSpellvớiPower(int).Use()của struct này trả về:Casts ice shard for [Power] damageStruct
PowerStrikevớiMultiplier(int).Use()của struct này trả về:Performs power strike at [Multiplier]x damageThêm trường
ability(kiểuAbility) vào struct Mage, cùng các phương thứcSetAbility(a Ability)vàUseAbility() string. UseAbility trả vềNo ability setnếu ability là nil.main.go: Tạo nhân vật và minh họa cơ chế chiến đấu.Đọc kiểu nhân vật (
warrior,magehoặcarcher), rồi đọc tên, máu và các chỉ số riêng của kiểu nhân vật đó. Tạo nhân vật tương ứng.Sau đó, đọc số lượng hành động. Mỗi hành động là một trong các mục sau:
status: in trạng thái của nhân vậtattack: in[Name] attacks for [damage] damagedefend [amount]: áp dụng sát thương và in[Name] takes [actual] damagesetability [type] [power]: chỉ dành cho mage, đặt năng lực (type làfire,icehoặcstrike)useability: chỉ dành cho mage, in kết quả của Use() trong năng lực
Chương trình sẽ nhận các đầu vào sau:
- Kiểu nhân vật, tên, máu và các chỉ số riêng của kiểu nhân vật đó
- Số lượng hành động, sau đó là từng hành động trên một dòng riêng
Ví dụ, với đầu vào:
warrior
Thorin
100
15
8
5
status
attack
defend 20
status
defend 100Đầu ra sẽ là:
Thorin: 100/100 HP
Thorin attacks for 15 damage
Thorin takes 12 damage
Thorin: 88/100 HP
Thorin takes 92 damageVới đầu vào:
mage
Gandalf
80
25
10
6
attack
useability
setability fire 30
useability
setability ice 25
useability
defend 40Đầu ra sẽ là:
Gandalf attacks for 50 damage
No ability set
Casts fireball for 30 damage
Casts ice shard for 25 damage
Gandalf takes 35 damageVới đầu vào:
archer
Legolas
90
18
15
4
status
attack
defend 24
statusĐầu ra sẽ là:
Legolas: 90/90 HP
Legolas attacks for 23 damage
Legolas takes 18 damage
Legolas: 72/90 HPHãy chú ý cách nhúng struct giúp mỗi kiểu nhân vật dùng chung các trường và phương thức cơ sở của Character, còn interface cho phép đa hình trong chiến đấu, nhờ đó bất kỳ Attacker nào cũng có thể gây sát thương và bất kỳ Defender nào cũng có thể nhận sát thương. Mẫu Strategy thể hiện rõ ưu điểm qua các năng lực có thể thay đổi của Mage: cùng một nhân vật có thể dùng các phép khác nhau mà không cần thay đổi phần triển khai cốt lõi!
Tự làm thử
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Đọc loại nhân vật
scanner.Scan()
charType := strings.TrimSpace(scanner.Text())
// Đọc tên nhân vật
scanner.Scan()
name := strings.TrimSpace(scanner.Text())
// Đọc lượng máu
scanner.Scan()
health, _ := strconv.Atoi(strings.TrimSpace(scanner.Text()))
// Đọc chỉ số đầu tiên theo loại nhân vật
scanner.Scan()
stat1, _ := strconv.Atoi(strings.TrimSpace(scanner.Text()))
// Đọc chỉ số thứ hai theo loại nhân vật
scanner.Scan()
stat2, _ := strconv.Atoi(strings.TrimSpace(scanner.Text()))
// TODO: Tạo nhân vật phù hợp dựa trên charType
// Sử dụng các biến (variable): charType, name, health, stat1, stat2
// Với warrior: stat1 = Strength, stat2 = Armor
// Với mage: stat1 = Intelligence, stat2 = MagicResist
// Với archer: stat1 = Agility, stat2 = Evasion
// Đọc số lượng hành động
scanner.Scan()
numActions, _ := strconv.Atoi(strings.TrimSpace(scanner.Text()))
// Xử lý từng hành động
for i := 0; i < numActions; i++ {
scanner.Scan()
line := strings.TrimSpace(scanner.Text())
parts := strings.Fields(line)
action := parts[0]
// TODO: Xử lý từng loại hành động:
// - "status": in ra Status() của nhân vật
// - "attack": in ra "[Name] attacks for [damage] damage"
// - "defend [amount]": áp dụng sát thương và in ra "[Name] takes [actual] damage"
// - "setability [type] [power]": với pháp sư, thiết lập kỹ năng (fire, ice hoặc strike)
// - "useability": với pháp sư, in ra kết quả của UseAbility()
_ = action // Xóa dòng này khi triển khai
_ = fmt.Println // Xóa dòng này khi triển khai
}
}
Tất cả bài học trong Lập trình hướng đối tượng
1Nền tảng OOP trong Go
Tệp bên ngoàiWorkspace và module trong GoPackage và importTên được export và không được exportGiới thiệu về OOP trong GoStruct như lớpĐịnh nghĩa phương thức cho structReceiver kiểu con trỏ và kiểu giá trịKhởi tạo structHàm khởi tạoÔn tập: máy tính đơn giản4Interface
Giới thiệu về interfaceTriển khai ngầm địnhInterface như một hợp đồngInterface rỗng (any)Khẳng định kiểuswitch kiểuKết hợp interfaceInterface Stringer và ErrorÔn tập: Máy tính hình học7Tính đóng gói
Trường được xuất và không được xuấtĐóng gói ở cấp packagePhương thức getter và setterẨn thông tin trong GoÔn tập: hồ sơ học sinh10Generics (Go 1.18 trở lên)
Giới thiệu về GenericsTham số kiểuRàng buộc kiểuStruct tổng quátGiải pháp thay thế cho phương thức tổng quátÔn tập: tập hợp tổng quát13Các mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếMẫu SingletonMẫu FactoryMẫu Abstract FactoryMẫu ObserverMẫu Strategy2Tìm hiểu chuyên sâu về kiểu dữ liệu và struct
Kiểu cơ bản và kiểu tổng hợpĐịnh nghĩa kiểu dữ liệu tùy chỉnhThẻ structStruct ẩn danhStruct lồng nhauGiá trị zero và giá trị mặc địnhÔn tập: sổ liên lạc5Ưu tiên kết hợp hơn kế thừa
Vì sao Go không có kế thừaNhập môn nhúng structTự động nâng phương thứcNhúng nhiều structNhúng và kết tậpChe khuất phương thức được nhúngÔn tập: hệ phân cấp nhân viên8Xử lý lỗi và lập trình hướng đối tượng
Giao diện errorCác kiểu lỗi tùy chỉnhBọc lỗi với fmt.ErrorfLỗi sentinelerrors.Is() và errors.As()Panic, defer và recoverÔn tập: trình phân tích tệp3Con trỏ và bộ nhớ
Cơ bản về con trỏ trong GoCon trỏ đến structTruyền theo giá trị và tham chiếuHàm new()Thu gom rác trong GoÔn tập: xây dựng danh sách liên kết6Đa hình trong Go
Đa hình qua interfaceĐịnh kiểu vịt trong GoQuy tắc thỏa mãn interfaceCác tập hợp đa hìnhTiêm phụ thuộcÔn tập: bộ xử lý thanh toán9Lập trình đồng thời và lập trình hướng đối tượng
Cơ bản về goroutineChannel và giao tiếpChannel có bộ đệm và không có bộ đệmCâu lệnh selectsync.Mutex và sync.RWMutexsync.WaitGroupThiết kế struct an toàn luồngÔn tập: worker poolTự luyện tập: Trình biên dịch Go online