ゲームキャラクターシステム
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 107/107。
チャレンジ
簡単構造体の埋め込み、インターフェース、そしてストラテジーパターンを組み合わせて、柔軟な戦闘メカニズムを備えたゲームキャラクターシステムを構築しましょう!実行時に切り替え可能なアビリティを持ち、独自の方法で攻撃や防御を行うさまざまなキャラクタータイプを設計します。
コードは以下の5つのファイルに分けて構成します:
character.go: 共通のフィールドを持つベースとなるCharacter構造体を定義します:Name(string)、Health(int)、MaxHealth(int)。NewCharacterコンストラクタと以下のメソッドを含めます:IsAlive() bool— 体力が0より大きい場合に true を返しますStatus() string—[Name]: [Health]/[MaxHealth] HPという形式で返します
interfaces.go: ポリモーフィズムを可能にする戦闘インターフェースを定義します:Attack() intを持つAttackerインターフェース — 与えたダメージを返しますTakeDamage(damage int) intを持つDefenderインターフェース — ダメージ軽減を適用し、実際に受けたダメージを返しますUse() stringを持つAbilityインターフェース — アビリティ効果の説明を返します
characters.go:Characterを埋め込み、AttackerとDefenderの両方を実装する3つのキャラクタータイプを作成します:Warrior—Strength(int) とArmor(int) フィールドを持ちます。攻撃は Strength をダメージとして返します。TakeDamage は受けるダメージを Armor 分だけ軽減します(最低1ダメージ)。Mage—Intelligence(int) とMagicResist(int) フィールドを持ちます。攻撃は Intelligence * 2 をダメージとして返します。TakeDamage は受けるダメージを MagicResist / 2 分だけ軽減します(最低1ダメージ)。Archer—Agility(int) とEvasion(int) フィールドを持ちます。攻撃は Agility + 5 をダメージとして返します。TakeDamage:Evasion > 10 の場合、ダメージを25%軽減し(整数除算)、それ以外の場合は全ダメージを受けます(最低1ダメージ)。それぞれのコンストラクタ
NewWarrior、NewMage、NewArcherを含め、名前、体力、およびそれぞれの固有ステータス値を受け取るようにします。abilities.go: 切り替え可能なアビリティを使用してストラテジーパターンを実装します:Power(int) を持つFireSpell構造体。そのUse()はCasts fireball for [Power] damageを返します。Power(int) を持つIceSpell構造体。そのUse()はCasts ice shard for [Power] damageを返します。Multiplier(int) を持つPowerStrike構造体。そのUse()はPerforms power strike at [Multiplier]x damageを返します。Mage 構造体に
abilityフィールド(Ability型)を追加し、SetAbility(a Ability)とUseAbility() stringメソッドを実装します。UseAbility は、アビリティが nil の場合、No ability setを返します。main.go: キャラクターを作成し、戦闘メカニズムを実演します。キャラクタータイプ(
warrior、mage、またはarcher)を読み込み、次にキャラクターの名前、体力、およびタイプ固有のステータスを読み込みます。適切なキャラクターを作成します。次にアクションの数を読み込みます。各アクションは以下のいずれかです:
status— キャラクターのステータスを表示しますattack—[Name] attacks for [damage] damageと表示しますdefend [amount]— ダメージを適用し、[Name] takes [actual] damageと表示しますsetability [type] [power]— Mage の場合のみ、アビリティを設定します(タイプはfire、ice、またはstrike)useability— Mage の場合のみ、アビリティの 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構造体の埋め込みによって、各キャラクタータイプがベースとなる 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())
// 体力を読み込む
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復習 - ファイルパーサー