오늘 배운 내용
-
개인 과제 오류 수정 1 - 프로퍼티
-
개인 과제 오류 수정 2 - 함수 호출
-
개인 과제 선택 요구 사항 시작
-
인벤토리 정렬하기
-
상점 아이템 구매, 판매
-
장착 개선, 던전 입장
개인 과제 오류 수정 1 - 프로퍼티
저번주에 아이템 리스트를 출력하는 과정에서 스택 오버플로우가 발생했다.
코드를 살펴보니까 아이템의 이름을 호출하는 Name 프로퍼티에서 오류가 발생했었다.
오류가 생긴 이유는 처음에 Name 프로퍼티를 선언할 때는 자동 프로퍼티로 선언했었는데
장착한 아이템의 이름 앞에 E를 붙히면서 Name 프로퍼티의 get을 수정했었는데
수정을 하면서 따로 변수를 만들어주지 않고 그대로 Name 프로퍼티 안에서 Name 프로퍼티를 호출해버렸다.
그 결과 무한 루프에 빠지게 되어서 스택 오버플로우가 발생했고 새로 변수를 만들어 할당해주니 바로 해결되었다.
public string Name
{
get
{
StringBuilder sb = new StringBuilder();
if(IsEquip)
{
sb.Append($"[E]");
}
sb.Append($"{_name}");
return sb.ToString();
}
private set { _name = value; }
}
## 개인 과제 오류 수정 2 - 함수 호출
다음으로 고친 버그는 문자열을 리턴하는 함수를 호출했는데 출력된 메세지가
System.Func ~ 어쩌고가 출력 되었다. 이게 왜 출력되는지 이리저리 찾아보다보니
Func 즉, 델리게이트 형식의 데이터가 String으로 출력되고 있었다.
원인은 String을 return하는 함수를 호출할때 () 괄호를 뒤에 제대로 붙히지 않았고
그래서 함수를 호출한게 아니라 마치 델리게이트에 함수를 넣어줄때 처럼 함수를 넣어준 것이 되었고
그결과 System.Func ~ 같은 형태가 콘솔에 출력된 것 이였다.
해결방법은 당연히 괄호를 넣어주니 해결되었다..
개인 과제 선택 요구 사항 시작
선택 요구 사항을 확인해보니 1번부터 5번까지는 이미 거의 구현이 되어있었다.
- 아이템 정보를 클래스 구조체로 활용하기
- 아이템 정보를 배열로 관리하기
- 인벤토리 크기 맞춤
위 3가지는 처음 만들때 부터 구현을 해버렸다.
- 아이템 추가하기
- 콘솔 꾸미기
그리고 이 두가지는 마지막에 하는게 좋을 것 같아서 미뤘다.
- 인벤토리 정렬하기
- 상점 아이템 구매, 판매
- 장착 개선
- 던전 입장
그래서 이 4가지 기능을 오늘 구현해 보았다.
인벤토리 정렬하기
인벤토리 정렬하기 기능은 인벤토리를 입력에 따라서
이름 길이, 장착 여부, 공격력, 방어력에 따라 정렬되도록 만드는 것 이였다.
나는 Item들을 인벤토리에서 List
List는 Sort()메서드로 정렬기능을 지원하기 때문에 Sort에 넣어줄 비교 연산 메서드를 만들어 주면 되었다.
Item클래스가 IComparable 인터페이스를 상속받고 CompareTo() 메소드를 구현해주기만 하면
Sort로 비교가 가능했는데 나는 4가지를 선택해서 비교해야 됬기 때문에
Enum을 이용해서 인벤토리를 정렬할 때 Enum 값을 받아서 해당되는 요소를 비교하는 함수를 따로 만들어 주었다
public enum ItemType
{
Weapon,
Armor,
}
public enum SortingInventory
{
Name,
Equipment,
Attack,
Defense,
}
class Item : IComparable<Item>
{
public Item(string _name, ItemType _type, string _Info, int _price)
{
Name = _name;
Type = _type;
Info = _Info;
Price = _price;
IsEquip = false;
Attack = 0;
Defense = 0;
IsSell = false;
}
private string _name;
public string Name
{
get
{
StringBuilder sb = new StringBuilder();
if(IsEquip)
{
sb.Append($"[E]");
}
sb.Append($"{_name}");
return sb.ToString();
}
private set { _name = value; }
}
public ItemType Type { get; private set; }
public string Info { get; private set; }
public bool IsEquip { get; set; }
public int Attack { get; set; }
public int Defense { get; set; }
public int Price { get; private set; }
public bool IsSell { get; set; }
public int CompareTo(Item? other)
{
return CompareBySortingInventory(other, SortingInventory.Name);
}
public int CompareBySortingInventory(Item? other, SortingInventory type)
{
switch (type)
{
case SortingInventory.Name:
return Name.CompareTo(other.Name);
case SortingInventory.Equipment:
return -IsEquip.CompareTo(other.IsEquip);
// true가 위로 가기 위해서 CompareTo가 return한 int 값에 -1을 곱해서 방향을 바꿔준다.
case SortingInventory.Attack:
return Attack.CompareTo(other.Attack);
case SortingInventory.Defense:
return Defense.CompareTo(other.Defense);
default:
return Name.CompareTo(other.Name);
}
}
}
Item 클래스를 위 처럼 작성하였고 Inventory 클래스에서 메서드를 통해서 호출해 주었다.
public void SortInventory(SortingInventory type)
{
itemlist.Sort((x, y) => x.CompareBySortingInventory(y, type));
}
상점 기능
Inventory를 상속받아서 Shop 클래스를 만들었다.
class Shop : Inventory
{
public Shop()
{
AddItem(new Armor("수련자 갑옷", ItemType.Armor, "수련에 도움을 주는 갑옷입니다.", 1000, 5));
AddItem(new Armor("무쇠갑옷", ItemType.Armor, "무쇠로 만들어져 튼튼한 갑옷입니다.", 500, 9));
AddItem(new Armor("스파르타의 갑옷", ItemType.Armor, "스파르타의 전사들이 사용했다는 전설의 갑옷입니다.", 3500, 15));
AddItem(new Weapon("낡은 검", ItemType.Weapon, "쉽게 볼 수 있는 낡은 검입니다.", 600, 2));
AddItem(new Weapon("청동 도끼", ItemType.Weapon, "쉽게 볼 수 있는 낡은 검입니다.", 1500, 5));
AddItem(new Weapon("스파르타의 창", ItemType.Weapon, "쉽게 볼 수 있는 낡은 검입니다.", 4000, 7));
}
public new string MakeItemList()
{
StringBuilder str = new StringBuilder();
str.Append("[아이템 목록]\n");
foreach (Item _item in itemlist)
{
str.Append("- ");
str.Append($"{MakeItemInfo(_item)} |");
str.Append("\n");
}
return str.ToString();
}
public new string MakeItemInfo(Item _item)
{
StringBuilder str = new StringBuilder();
str.Append(base.MakeItemInfo(_item));
str.Append(_item.IsSell ? "구매완료" : $"{_item.Price} G");
return str.ToString();
}
public string MakeShopList()
{
StringBuilder str = new StringBuilder();
str.Append("[아이템 목록]\n");
int index = 1;
foreach (Item _item in itemlist)
{
str.Append($"- {index++} ");
if (_item.Type == ItemType.Weapon)
{
Weapon weapon = (Weapon)_item;
str.Append(MakeItemInfo(weapon));
}
else
{
Armor armor = (Armor)_item;
str.Append(MakeItemInfo(armor));
}
str.Append("\n");
}
return str.ToString();
}
public Item? BuyItem(int index)
{
if (itemlist[index].IsSell)
{
Console.WriteLine("이미 구매한 아이템입니다.");
return null;
}
if (DungeonGame.player.Gold >= itemlist[index].Price)
{
DungeonGame.player.Gold -= itemlist[index].Price;
itemlist[index].IsSell = true;
Console.WriteLine("구매를 완료했습니다.");
return itemlist[index];
}
else
{
Console.WriteLine("Gold 가 부족합니다.");
return null;
}
}
}
Inventory에 있던 메서드들을 사용할 수 없나 싶었지만
결국에는 마지막에 가격을 붙히는 부분이 문제가되어서 메서드를 오버라이딩해서 사용했다.
그냥 덮어 쓰려고 했는데 IDE에서 부모의 메서드를 숨길꺼면 new를 그냥 붙혀! 라고 해서
new 키워드를 사용해서 오버라이딩을 했다.
장착 개선 ,던전 입장
장착 개선은 같은 종류의 아이템을 동시에 장착하지 못하도록 하는 기능 이였다.
사실 그전에 EquipItem() 메서드를 좀 수정했어서, 장착 개선 부분은 변수를 추가하고 코드를 한 두줄 추가하는 것으로 쉽게 구현할 수 있었다.
public Item EquipWeapon { get; set; }
public Item EquipArmor { get; set; }
public void EquipItem(int i)
{
Item target = Inventory.PeekItem(i);
if (target.IsEquip)
{
target.IsEquip = false;
AddAttack -= target.Attack;
AddDefense -= target.Defense;
}
else
{
if (target.Type == ItemType.Weapon)
{
if(EquipWeapon != null)
{
EquipWeapon.IsEquip = false;
}
EquipWeapon = target;
}
else
{
if(EquipArmor != null)
{
EquipArmor.IsEquip = false;
}
EquipArmor = target;
}
target.IsEquip = true;
AddAttack += target.Attack;
AddDefense += target.Defense;
}
}
던전 입장 부분은 던전이 클리어 텍스트만 나오는 형태길래
따로 클래스를 만들지는 않고 그냥 하드 코딩으로만 만들어줬다
나중에 시간이 많이 남는다면 따로 클래스를 만들어서 만들어보려고한다.