ゲームキャラクターシステム
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 107/107。
チャレンジ
簡単struct 埋め込み、インターフェース、Strategy パターンを組み合わせて、柔軟な戦闘メカニクスを実現する Game Character System を構築しましょう!それぞれ異なる方法で攻撃と防御を行い、実行時に入れ替え可能な abilities を持つ、さまざまなキャラクタータイプを設計します。
コードを5つのファイルに分けて整理します。
character.go: 共通の Fields を持つ基本Characterstruct を Define します。Fields はName(string)、Health(int)、MaxHealth(int) です。NewCharacterconstructor と、次の methods を含めます。IsAlive() bool: health が 0 より大きい場合に true を返しますStatus() string: 次の format を返します:[Name]: [Health]/[MaxHealth] HP
interfaces.go: polymorphism を可能にする combat interfaces を Define します。Attackerinterface とAttack() int: dealt した damage を返しますDefenderinterface とTakeDamage(damage int) int: damage reduction を apply し、実際に受けた damage を返しますAbilityinterface とUse() string: ability の effect の description を返します
characters.go:Characterを embed し、AttackerとDefenderの両方を実装する3つの character types を Create します。Warrior:Strength(int) とArmor(int) の field を持ちます。Attack は Strength を damage として返します。TakeDamage は incoming damage を Armor 分減らします(受ける damage は minimum 1)。Mage:Intelligence(int) とMagicResist(int) の fields を持ちます。Attack は Intelligence * 2 を damage として返します。TakeDamage は incoming damage を MagicResist / 2 分減らします(受ける damage は minimum 1)。Archer:Agility(int) とEvasion(int) の fields を持ちます。Attack は Agility + 5 を damage として返します。TakeDamage: Evasion > 10 の場合は damage を 25% 減らし(整数除算)、それ以外の場合は full damage を受けます(受ける damage は minimum 1)。それぞれの constructor、
NewWarrior、NewMage、NewArcherを含めます。これらは name、health、および各 character 固有の stat values を受け取ります。abilities.go: 入れ替え可能な abilities を使って Strategy パターンを実装します。FireSpellstruct とPower(int) を定義します。そのUse()は次を返します:Casts fireball for [Power] damageIceSpellstruct とPower(int) を定義します。そのUse()は次を返します:Casts ice shard for [Power] damagePowerStrikestruct とMultiplier(int) を定義します。そのUse()は次を返します:Performs power strike at [Multiplier]x damageMage struct に(type が
Abilityの)abilityfield を追加し、SetAbility(a Ability)とUseAbility() stringmethods を実装します。ability が nil の場合、UseAbility はNo ability setを返します。main.go: characters を Create し、combat mechanics を実演します。character type(
warrior、mage、またはarcher)を読み取り、続けて character の name、health、type-specific stats を読み取ります。appropriate な character を Create します。次に actions の number を読み取ります。各 action は次のいずれかです。
status: character の status を print しますattack:[Name] attacks for [damage] damageを print しますdefend [amount]: damage を apply し、[Name] takes [actual] damageを print しますsetability [type] [power]: mages のみ、ability を set します(type はfire、ice、またはstrike)useability: mages のみ、ability の Use() result を print します
次の inputs が提供されます。
- Character type、name、health、および type-specific stats
- actions の number、その後に各 action を separate line で指定
たとえば、次の入力が与えられた場合:
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 HPstruct embedding によって、各 character type が基本の Character の fields と methods を共有できることに注目してください。一方、interfaces は polymorphic な combat を可能にし、任意の Attacker が damage を与え、任意の Defender がそれを受けられるようにします。Strategy パターンは、Mage の入れ替え可能な abilities で特に効果を発揮します。同じ character が、core implementation を変更せずに異なる spells を使えるのです。
自分で試してみよう
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()))
// 1つ目のタイプ固有ステータスを読み取る
scanner.Scan()
stat1, _ := strconv.Atoi(strings.TrimSpace(scanner.Text()))
// 2つ目のタイプ固有ステータスを読み取る
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]": メイジの場合、アビリティを設定する (fire, ice, または strike)
// - "useability": メイジの場合、UseAbility() の結果を出力する
_ = action // 実装時にこの行を削除する
_ = fmt.Println // 実装時にこの行を削除する
}
}
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー自分で練習してみよう: Goオンラインコンパイラ