텍스트 RPG 과제 코드
과제 답지가 있다는 이야기를 듣고서 이번 과제는 답지를 내 방식대로 조금 더 개선하는 방향으로 해야겠다고 생각했다. 답지에 있는 코드를 분석하고 문제점을 찾아 개선해봤다.
1. 인터페이스 중복 구현 문제
첫 번째 문제점은 ICharacter를 상속받는 클래스에 중복 구현할 필요가 없는 인터페이스의 함수와 프로퍼티를 계속 중복 구현한다는 것이다. 이 문제를 해결하기 위해 .NET 8.0 부터 기본 구현 기능을 제공하지만 내 개발 환경은 .NET 6.0이며 이 기능을 사용할 수 없다. 대신에 ICharacter 를 상속받는 Character 클래스를 만들어서 필요한 것만 오버라이드 할 수 있도록 함수와 변수의 기본 구현을 해뒀고, 기존에 ICharacter를 상속받는 클래스는 Character 클래스를 상속받게 만들어 기본 구현 기능과 동일한 효과를 낼 수 있도록 만들었다.
public class Character : ICharacter
{
public string name { get; }
public int health { get; set; }
public int maxHealth { get; }
public bool isDead => health <= 0;
public int attack => new Random().Next(minAttack, maxAttack);
public int minAttack { get; set; }
public int maxAttack { get; set; }
public Character(string name, int maxHealth, int minAttack, int maxAttack)
{
this.name = name;
this.maxHealth = maxHealth;
this.health = maxHealth;
this.minAttack = minAttack;
this.maxAttack = maxAttack;
}
public void takeDamage(int damage)
{
health -= damage;
if (isDead)
{
Console.WriteLine($"{name}이(가) 죽었습니다.");
}
else
{
Console.WriteLine($"{name}이(가) {damage}의 데미지를 받았습니다. 남은 체력: {health}/{maxHealth}");
}
}
}
public class Warrior : Character
{
public Warrior(string name)
: base(name, 100, 10, 20)
{
}
public void Use(IItem item)
{
item.OnUsed(this);
}
}
2. Item 사용 방식
두 번째 문제점은 Item 사용 방식이다. 기존 코드는 아래와 같다.
item.Use(Character);
이런 방식으로 호출하면 의미적으로 아이템이 캐릭터를 사용한게 된다. 물론 유도리있게 넘어갈 수 있고, 이 코드를 보며 그 누구도 오해하지 않지만 이런 식의 사용 방법은 다른 불편함이 존재한다.
배경지식이 아예 없는 다른 사람이 캐릭터가 아이템을 사용할 수 있도록 코드를 만드려고 하는 상황을 상상해보자. 첫 번째로 개발자는 본능적으로 캐릭터에서 `UseItem`이라는 함수가 있는지 찾을 것이다. 하지만 캐릭터는 아이템을 사용할 수 있는 함수를 제공하지 않는다. 여기서 개발자는 "아이템을 사용하는 다른 시스템이 있는건가?" 라는 의문을 가질 것이고 캐릭터가 아이템을 사용하게 만들기 위해 아이템의 전체적인 코드를 파악해야만 한다. 이것은 매우 심각한 자원의 낭비라고 생각한다.
따라서 우리는 무언가 만들 때 그것을 사용하는 사람의 편의성을 항상 고민해야 한다. 내가 개선한 코드는 아래와 같다.
public class Warrior : Character
{
public Warrior(string name)
: base(name, 100, 10, 20)
{
}
public void Use(IItem item)
{
item.OnUsed(this);
}
}
public interface IItem
{
string Name { get; }
void OnUsed(ICharacter target);
}
이렇게 만든다면 코드를 읽을 때에도 명확하게 읽혀지며 아이템을 구현할 때 기존과 다르지 않게 만들 수 있으며 아이템 변수에서 직접 사용 함수를 호출하는 방식과 캐릭터가 아이템을 사용해주는 방식 둘 다 사용할 수 있다. 또, 원래는 매개변수가 ICharacter가 아니라 플레이어의 캐릭터 클래스인 Warrior였는데, ICharacter로 바꿔줌으로써 다른 몬스터나 캐릭터가 아이템을 사용할 여지를 남겨놨다. 추가적으로, `OnUsed`라는 이름을 사용하므로 함수의 호출 시점까지 명확하게 인지할 수 있다는 장점이 있다.
과제 전체 코드
using System.Collections.Generic;
using System.Threading;
using System;
public interface ICharacter
{
string name { get; }
int health { get; set; }
int maxHealth { get; }
int attack { get; }
int minAttack { get; set; }
int maxAttack { get; set; }
bool isDead { get; }
void takeDamage(int damage);
}
public class Character : ICharacter
{
public string name { get; }
public int health { get; set; }
public int maxHealth { get; }
public bool isDead => health <= 0;
public int attack => new Random().Next(minAttack, maxAttack);
public int minAttack { get; set; }
public int maxAttack { get; set; }
public Character(string name, int maxHealth, int minAttack, int maxAttack)
{
this.name = name;
this.maxHealth = maxHealth;
this.health = maxHealth;
this.minAttack = minAttack;
this.maxAttack = maxAttack;
}
public void takeDamage(int damage)
{
health -= damage;
if (isDead)
{
Console.WriteLine($"{name}이(가) 죽었습니다.");
}
else
{
Console.WriteLine($"{name}이(가) {damage}의 데미지를 받았습니다. 남은 체력: {health}/{maxHealth}");
}
}
}
public class Warrior : Character
{
public Warrior(string name)
: base(name, 100, 10, 20)
{
}
public void Use(IItem item)
{
item.OnUsed(this);
}
}
public class Monster : Character
{
public Monster(string name, int maxHealth, int minAttack, int maxAttack)
: base(name, maxHealth, minAttack, maxAttack)
{
}
}
public class Goblin : Monster
{
public Goblin(string name)
: base(name, 50, 5, 10)
{
}
}
public class Dragon : Monster
{
public Dragon(string name)
: base(name, 100, 10, 20)
{
}
}
public interface IItem
{
string Name { get; }
void OnUsed(ICharacter target);
}
public class HealthPotion : IItem
{
public string Name => "체력 포션";
public void OnUsed(ICharacter target)
{
Console.WriteLine("체력 포션을 사용합니다. 체력이 50 증가합니다.");
target.health += 50;
if (target.health > target.maxHealth)
{
target.health = target.maxHealth;
}
}
}
public class StrengthPotion : IItem
{
public string Name => "공격력 포션";
public void OnUsed(ICharacter target)
{
Console.WriteLine("공격력 포션을 사용합니다. 공격력이 10 증가합니다.");
target.maxAttack += 10;
}
}
public class Stage
{
private Warrior player;
private ICharacter monster;
private List<IItem> rewards;
// 이벤트 델리게이트 정의
public delegate void GameEvent(ICharacter character);
public event GameEvent OnCharacterDeathDelegate;
public Stage(Warrior player, ICharacter monster, List<IItem> rewards)
{
this.player = player;
this.monster = monster;
this.rewards = rewards;
OnCharacterDeathDelegate += OnCharacterDeath;
}
public void Start()
{
Console.WriteLine($"스테이지 시작! 플레이어 정보: 체력({player.health}), 공격력({player.attack})");
Console.WriteLine($"몬스터 정보: 이름({monster.name}), 체력({monster.health}), 공격력({monster.attack})");
Console.WriteLine("----------------------------------------------------");
while (true)
{
DoTurn(player, monster);
if (monster.isDead)
{
OnCharacterDeathDelegate?.Invoke(monster);
break;
}
DoTurn(monster, player);
if (player.isDead)
{
OnCharacterDeathDelegate?.Invoke(player);
break;
}
}
}
void DoTurn(ICharacter turnOwner, ICharacter enemy)
{
Console.WriteLine($"{turnOwner.name}의 턴!");
enemy.takeDamage(turnOwner.attack);
Console.WriteLine();
Thread.Sleep(1000);
}
private void OnCharacterDeath(ICharacter character)
{
if (character is Monster)
{
Console.WriteLine($"스테이지 클리어! {character.name}를 물리쳤습니다!");
if (rewards != null)
{
Console.WriteLine("아래의 보상 아이템 중 하나를 선택하여 사용할 수 있습니다:");
foreach (var item in rewards)
{
Console.WriteLine(item.Name);
}
Console.WriteLine("사용할 아이템 이름을 입력하세요:");
string input = Console.ReadLine();
// 선택된 아이템 사용
IItem selectedItem = rewards.Find(item => item.Name == input);
if (selectedItem != null)
{
player.Use(selectedItem);
}
}
player.health = player.maxHealth;
}
else
{
Console.WriteLine("게임 오버! 패배했습니다...");
}
}
}
class Program
{
static void Main(string[] args)
{
Warrior player = new Warrior("Player");
Goblin goblin = new Goblin("Goblin");
Dragon dragon = new Dragon("Dragon");
List<IItem> stage1Rewards = new List<IItem> { new HealthPotion(), new StrengthPotion() };
List<IItem> stage2Rewards = new List<IItem> { new StrengthPotion(), new HealthPotion() };
Stage stage1 = new Stage(player, goblin, stage1Rewards);
stage1.Start();
if (player.isDead) return;
Stage stage2 = new Stage(player, dragon, stage2Rewards);
stage2.Start();
if (player.isDead) return;
Console.WriteLine("축하합니다! 모든 스테이지를 클리어했습니다!");
}
}
'활동 > 내일배움캠프 Unity' 카테고리의 다른 글
[내배캠] TIL 텍스트 RPG 개인 과제 종료 후 팀 프로젝트 대비 (1) | 2024.09.26 |
---|---|
[내배캠] TIL CS8600 경고 (0) | 2024.09.25 |
[내배캠] TIL 권장하는 TIL 작성법 + 스네이크 게임 과제 코드 + 블랙잭 과제 코드 (0) | 2024.09.20 |
[내배캠] TIL 틱택토 만들기 (1) | 2024.09.19 |
[내배캠] TIL 미니프로젝트 발표 (1) | 2024.09.13 |