Menu
Coddy logo textTech

게임 캐릭터 시스템

Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 107번째.

challenge icon

챌린지

쉬움

구조체 임베딩(struct embedding), 인터페이스, 그리고 전략 패턴(Strategy pattern)을 결합하여 유연한 전투 메커니즘을 만드는 게임 캐릭터 시스템을 구축해 봅시다! 여러분은 런타임에 교체 가능한 능력을 갖추고, 고유한 방식으로 공격과 방어를 수행하는 다양한 캐릭터 유형을 설계하게 됩니다.

코드는 다음 다섯 개의 파일로 구성됩니다:

  • character.go: 공통 필드인 Name (string), Health (int), MaxHealth (int)를 가진 기본 Character 구조체를 정의합니다. 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를 모두 구현하는 세 가지 캐릭터 유형을 만듭니다:

    WarriorStrength (int)와 Armor (int) 필드를 가집니다. Attack은 Strength를 데미지로 반환합니다. TakeDamage는 들어오는 데미지에서 Armor만큼을 차감합니다 (최소 1의 데미지를 입음).

    MageIntelligence (int)와 MagicResist (int) 필드를 가집니다. Attack은 Intelligence * 2를 데미지로 반환합니다. TakeDamage는 들어오는 데미지에서 MagicResist / 2만큼을 차감합니다 (최소 1의 데미지를 입음).

    ArcherAgility (int)와 Evasion (int) 필드를 가집니다. Attack은 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는 ability가 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] — 마법사 전용으로, 능력을 설정합니다 (type은 fire, ice, 또는 strike).
    • useability — 마법사 전용으로, 능력의 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())

	// 체력(health) 읽기
	scanner.Scan()
	health, _ := strconv.Atoi(strings.TrimSpace(scanner.Text()))

	// 첫 번째 타입별 스탯 읽기
	scanner.Scan()
	stat1, _ := strconv.Atoi(strings.TrimSpace(scanner.Text()))

	// 두 번째 타입별 스탯 읽기
	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 // 구현 시 이 라인을 제거하세요
	}
}

객체 지향 프로그래밍의 모든 레슨