Menu
Coddy logo textTech

ゲームキャラクターシステム

CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 107/107。

challenge icon

チャレンジ

簡単

構造体の埋め込み、インターフェース、そしてストラテジーパターンを組み合わせて、柔軟な戦闘メカニズムを備えたゲームキャラクターシステムを構築しましょう!実行時に切り替え可能なアビリティを持ち、独自の方法で攻撃や防御を行うさまざまなキャラクタータイプを設計します。

コードは以下の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 を埋め込み、AttackerDefender の両方を実装する3つのキャラクタータイプを作成します:

    WarriorStrength (int) と Armor (int) フィールドを持ちます。攻撃は Strength をダメージとして返します。TakeDamage は受けるダメージを Armor 分だけ軽減します(最低1ダメージ)。

    MageIntelligence (int) と MagicResist (int) フィールドを持ちます。攻撃は Intelligence * 2 をダメージとして返します。TakeDamage は受けるダメージを MagicResist / 2 分だけ軽減します(最低1ダメージ)。

    ArcherAgility (int) と Evasion (int) フィールドを持ちます。攻撃は Agility + 5 をダメージとして返します。TakeDamage:Evasion > 10 の場合、ダメージを25%軽減し(整数除算)、それ以外の場合は全ダメージを受けます(最低1ダメージ)。

    それぞれのコンストラクタ NewWarriorNewMageNewArcher を含め、名前、体力、およびそれぞれの固有ステータス値を受け取るようにします。

  • 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: キャラクターを作成し、戦闘メカニズムを実演します。

    キャラクタータイプ(warriormage、または archer)を読み込み、次にキャラクターの名前、体力、およびタイプ固有のステータスを読み込みます。適切なキャラクターを作成します。

    次にアクションの数を読み込みます。各アクションは以下のいずれかです:

    • status — キャラクターのステータスを表示します
    • attack[Name] attacks for [damage] damage と表示します
    • defend [amount] — ダメージを適用し、[Name] takes [actual] damage と表示します
    • setability [type] [power] — Mage の場合のみ、アビリティを設定します(タイプは fireice、または 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 // 実装時にこの行を削除する
	}
}

オブジェクト指向プログラミングのすべてのレッスン