Menu
Coddy logo textTech

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

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

challenge icon

チャレンジ

簡単

struct 埋め込み、インターフェース、Strategy パターンを組み合わせて、柔軟な戦闘メカニクスを実現する Game Character System を構築しましょう!それぞれ異なる方法で攻撃と防御を行い、実行時に入れ替え可能な abilities を持つ、さまざまなキャラクタータイプを設計します。

コードを5つのファイルに分けて整理します。

  • character.go: 共通の Fields を持つ基本 Character struct を Define します。Fields は Name (string)、Health (int)、MaxHealth (int) です。NewCharacter constructor と、次の methods を含めます。
    • IsAlive() bool: health が 0 より大きい場合に true を返します
    • Status() string: 次の format を返します: [Name]: [Health]/[MaxHealth] HP
  • interfaces.go: polymorphism を可能にする combat interfaces を Define します。
    • Attacker interface と Attack() int: dealt した damage を返します
    • Defender interface と TakeDamage(damage int) int: damage reduction を apply し、実際に受けた damage を返します
    • Ability interface と Use() string: ability の effect の description を返します
  • characters.go: Character を embed し、AttackerDefender の両方を実装する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、NewWarriorNewMageNewArcher を含めます。これらは name、health、および各 character 固有の stat values を受け取ります。

  • abilities.go: 入れ替え可能な abilities を使って Strategy パターンを実装します。

    FireSpell struct と Power (int) を定義します。その Use() は次を返します: Casts fireball for [Power] damage

    IceSpell struct と Power (int) を定義します。その Use() は次を返します: Casts ice shard for [Power] damage

    PowerStrike struct と Multiplier (int) を定義します。その Use() は次を返します: Performs power strike at [Multiplier]x damage

    Mage struct に(type が Ability の)ability field を追加し、SetAbility(a Ability)UseAbility() string methods を実装します。ability が nil の場合、UseAbility は No ability set を返します。

  • main.go: characters を Create し、combat mechanics を実演します。

    character type(warriormage、または 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 は fireice、または 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 HP

struct 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 // 実装時にこの行を削除する
	}
}

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

自分で練習してみよう: Goオンラインコンパイラ