오늘 배운 내용
-
5주차 - leetcode 84. Largest Rectangle in Histogram
-
개인 과제 시작
5주차 - leetcode 84번
// 결국은 구글링을했다...
// 만약 스택에 1,5,6이 있을 때
// 2가 들어오면 -> 5,6 의 넓이는 변하지 않는다.
// 7이 들어오면 -> 이전 값들과 상호 작용하여 더 큰 값을 만들어낸다.
// 하지만 계속 고민하던 부분인 -> 1,1,1,1,1,1,3,4,1,1,1,1,1 같은 경우는 어떻게 구분하냐?
// 스택에 인덱스 위치를 같이 저장해서 해결한다.
// 그렇다면 만약 스택에 { {1,1}, {5,2}, {6,3} } 이 있을때
// 새로운 값 {2,4}가 들어온다면
// 스택에서 2보다 큰값들을 전부 꺼냅니다. {5,2}, {6,3}
// {6.3}이 꺼내지고 6 * (새로운 값의 인덱스 - 6 자신의 인덱스) = 6 * (4-3) = 6 -> 넓이 최대값 갱신
// {5,2}가 꺼내지고 5 * (4 - 2) = 10 -> 넓이 최대값 갱신
// 그리고 이제 {2,4}를 스택에 넣는데.. 마지막에 pop된 {5,2}의 인덱스를 계승받는다
// {2,4} 보다 큰값들에 의해서 넓이가 늘어나기 떄문이다. 그래서 최종적으로 {2,2}를 스택에 저장하게 된다.
// 그리고 이것을 반복해서 마지막 히스토그램까지 확인했을 때
// 스택이 남아있는 데이터도 처리해주어야 한다.
// 스택을 순회하면서 높이 * (N - 자신의 인덱스)하고 최대값을 갱신한다.
public class Solution
{
public int LargestRectangleArea(int[] heights)
{
Stack<int[]> stack = new Stack<int[]>();
int maxSquare = 0;
for (int i = 0; i < heights.Length; i++)
{
int[] temp = new int[] { heights[i], i };
int[] tempPop = temp; // 만약 아무 Pop도 없었다면 자기 자신의 인덱스를 적용하기위해서
while (stack.Count != 0 &&stack.Peek()[0] >= temp[0])
{
tempPop = stack.Pop();
maxSquare = Math.Max(maxSquare, tempPop[0] * (i - tempPop[1]));
}
temp[1] = tempPop[1]; // 인덱스계승
stack.Push(temp);
}
// 스택에 남아있는 데이터 처리
foreach (int[] temp in stack)
{
maxSquare = Math.Max(maxSquare, temp[0] * (heights.Length - temp[1]));
}
return maxSquare;
}
}
튜터님이 주신 힌트로 스택을 어떻게 사용할지 몇시간동안 계속 고민하다가 결국에는
구글링을 해서 해답을 보게 됬다..
계속 고민하고 해결하지 못한 부분인 1,1,1,1,1,1,3,4,1,1,1,1,1 같이 작은 것을 기준으로 길게 늘어지는 부분은
stack에 높이만 저장하는 것이 아니라 인덱스도 같이 저장해서 현재 높이보다 높아서 Pop되는 마지막 요소의 인덱스를 현재 인덱스가 계승해서 stack에 저장하게 되는데
이 부분을 전혀 생각하지 못했었다.
높은 높이를 Pop하는 상황에서 Pop()되는 요소들이 이번 요소의 넓이에 영향을 미치기 때문에
이번 요소의 시작 인덱스를 앞서 이어져있는 부분의 인덱스로 설정해서 반영을 해주면 되는것 이였다!
개인 과제 시작
12시에 개인 과제가 발제 되었다.
과제의 내용은 이렇다.
과제 개요
1. 던전을 떠나기전 마을에서 장비를 구하는 게임을 텍스트로 구현합니다. (C# - Console App)
2. 상점의 아이템 중에서 나만의 장비를 구성하는 부분이 포인트입니다.
3. 장비는 여러개의 데이터가 함께 있는 만큼 객체나 구조체를 활용하는 편이 효율적 입니다.
(이름, 가격, 효과, 설명 등…)
4. 관련된 여러 데이터를 다루는 부분은 배열이 도움이 됩니다.
필수요구사항
1. 게임 시작 화면
2. 상태보기
3. 인벤토리
저번에 팀원분이 C#은 객체지향적으로 해야지! 라는 말씀을 하셨어서
이번에는 그런 부분을 좀 신경써서 과제를 진행해 보려고 했다.
그래서 먼저 게임을 실행할 DungeonGame 클래스를 만들었다.
namespace TextBased_Dungeon_Game
{
internal class DungeonGame
{
public SceneManager sceneManager;
public static Warrior player = new Warrior();
public DungeonGame()
{
GameInit();
GameStart();
}
public void GameInit()
{
sceneManager = new SceneManager();
}
public void GameStart()
{
int nextScene = 0;
while (true)
{
nextScene = sceneManager.SceneList((SceneType)nextScene).DrawScene();
}
}
}
}
화면 전환은 SceneManager를 통해서 모두 관리하도록 생각을 해봤다.
namespace TextBased_Dungeon_Game
{
public enum SceneType
{
StartScene,
StatusScene,
InventoryScene,
EndPoint,
}
class SceneManager
{
List<Scene> _scenes = new List<Scene>();
public SceneManager()
{
for (int i =0; i< (int)SceneType.EndPoint; i++)
{
// Enum이름으로 Type을 구분해서 List에 추가
// Reflection 사용법 익히기
}
_scenes.Add(new StartScene(SceneType.StartScene));
_scenes.Add(new StatusScene(SceneType.StatusScene));
_scenes.Add(new InventoryScene(SceneType.InventoryScene));
}
public Scene SceneList(SceneType _type)
{
return _scenes[(int)_type];
}
}
}
그리고 Scene 클래스를 상속하는 각 화면을 클래스로 만들어줬다.
이 클래스들은 SceneManager에서 List로 관리하면서 DungeonGame 객체가 호출하는 화면을 그릴 것이다.
namespace TextBased_Dungeon_Game
{
class Scene
{ // 인터페이스에서는 모든 멤버가 암시적으로 public이고,
// 클래스나 메서드에서는 모든 멤버가 암시적으로 internal이다.
//Type 리플렉션을 사용할때 인터페이스로는 형변환이 불가능하기 때문에
//인스턴스를 생성할수 있는 클래스로 변경하고 메서드를 가상메서드로 선언
string WrongInputText = "잘못된 입력입니다";
public Scene(SceneType _type) { Type = _type; }
SceneType Type { get; set; }
public virtual int DrawScene() { return -1; }
public virtual int InputKey(int[] options)
{
int input;
while (!int.TryParse(Console.ReadLine(), out input) || !options.Contains(input))
{
Console.WriteLine(WrongInputText);
}
return input;
}
}
class StartScene : Scene
{
public StartScene(SceneType _type) : base(_type) { }
int[] options = { 1, 2 };
string GameStartText = "스파르타 마을에 오신 여러분 환영합니다.\n이곳에서 던전으로 들어가기 전 활동을 할 수 있습니다. \n\n1. 상태 보기\n2. 인벤토리\n\n원하시는 행동을 입력해주세요.";
public override int DrawScene()
{
Console.Clear();
Console.WriteLine(GameStartText);
return InputKey(options);
}
}
class StatusScene : Scene
{
public StatusScene(SceneType _type) : base(_type) { }
string WrongInputText = "잘못된 입력입니다";
int[] options = { 0 };
public override int DrawScene()
{
Console.Clear();
Console.WriteLine(MakeStatusText());
return InputKey(options);
}
public string MakeStatusText()
{
return $"상태 보기\n캐릭터의 정보가 표시됩니다.\n\n{DungeonGame.player.PlayerInfo}0. 나가기\n\n원하시는 행동을 입력해주세요";
}
}
class InventoryScene : Scene
{
public InventoryScene(SceneType _type) : base(_type) { }
int[] options = { 0, 1 };
public override int DrawScene()
{
Console.Clear();
Console.WriteLine(MakeInventoryText());
return InputKey(options);
}
public string MakeInventoryText()
{
string itemlist = DungeonGame.player.InventoryInfo();// 아이템 리스트를 뽑아옴
return $"인벤토리\n보유 중인 아이템을 관리할 수 있습니다.\n\n[아이템 목록]\n\n{itemlist}\n\n0. 나가기\n\n원하시는 행동을 입력해주세요.";
}
}
}
그리고 Warrior클래스, Item클래스, Inventory클래스를 만들어줬다.
Warrior 객체는 Inventory 객체를 가지고있고, Inventory 객체는 Item 리스트를 가지고 있다.
namespace TextBased_Dungeon_Game
{
class Warrior
{
public Warrior()
{
Level = 1;
Chad = "전사";
Attack = 10;
Defense = 5;
Health = 100;
Gold = 1500;
Inventory.AddItem(new Weapon("낡은 검", ItemType.Weapon, "쉽게 볼 수 있는 낡은 검입니다.", 0, 2));
Inventory.AddItem(new Armor("무쇠갑옷", ItemType.Armor, "무쇠로 만들어져 튼튼한 갑옷입니다.", 0, 5));
}
Inventory Inventory = new Inventory();
public int Level { get; set; }
public string Chad { get; set; }
public int Attack { get; set; }
public int Defense { get; set; }
public int Health { get; set; }
public int Gold { get; set; }
public string PlayerInfo()
{
return $"Lv. {Level}\n\nChad({Chad})\n\n공격력: {Attack}\n\n방어력: {Defense}\n\n체력: {Health}\n\nGold: {Gold} G\n\n";
}
public string InventoryInfo()
{
return Inventory.MakeItemList();
}
}
}
namespace TextBased_Dungeon_Game
{
//아이템 리스트를 관리하는 클래스
internal class Inventory
{
List<Item> itemlist = new List<Item>();
public void AddItem(Item _item)
{
itemlist.Add(_item);
}
public void DeleteItem(Item _item)
{
itemlist.Remove(_item);
}
public void EquipItem(Item _item)
{
if (!_item.IsEquip)
{
_item.IsEquip = true;
}
}
public string MakeItemList()
{
StringBuilder str = new StringBuilder();
int index = 0;
foreach (Item _item in itemlist)
{
if (_item.Type == ItemType.Weapon)
{
Weapon weapon = (Weapon)_item;
str.Append($" - {index++}{_item.Name}| 공격력 +{weapon.Attack} | {_item.Info}\n");
}
else
{
Armor armor = (Armor)_item;
str.Append($" - {index++}{_item.Name}| 방어력 +{armor.Defense} | {_item.Info}\n");
}
}
return str.ToString();
}
}
}
namespace TextBased_Dungeon_Game
{
// 0. 아이템 이름
// 1. 아이템 계열 type
// 2. 아이템 능력치
// 3. 아이템 설명 text
// 4. 장착 중인가?
// 결국 이 아이템은 player가 가지고있는 inventory에 들어가게됨
// player는 inventory객체를 하나가지고 inventory객체는 아이템을 넣고 빼고 정렬할 수 있다.
public enum ItemType
{
Weapon,
Armor,
}
//
class Item
{
public Item(string _name, ItemType _type, string _Info, int _price)
{
Name = _name;
Type = _type;
Info = _Info;
Price = _price;
IsEquip = false;
}
public string Name
{
get
{
StringBuilder sb = new StringBuilder();
if (IsEquip)
{
sb.Append($"[E]{Name}");
}
else
{
sb.Append($"{Name}");
}
while(sb.Length < 12)
{
sb.Append(" ");
}
Console.WriteLine(sb.ToString());
return sb.ToString();
}
private set { }
}
public ItemType Type { get; private set; }
public string Info { get; private set; }
public bool IsEquip { get; set; }
public int Price { get; private set; }
}
class Weapon : Item
{
public Weapon(string _name, ItemType _type, string _Info, int _price, int _attack) : base(_name, _type, _Info, _price)
{
Attack = _attack;
}
public int Attack { get; private set;}
}
class Armor : Item
{
public Armor(string _name, ItemType _type, string _Info, int _price, int _defense) : base(_name, _type, _Info, _price)
{
Defense = _defense;
}
public int Defense { get; private set; }
}
}
그리고 Warrior, Inventory, Item 객체들이 반환하는 객체들의 정보를
Scene에서 불러서 string을 출력하도록 만들어 줬는데,
여기에서 에러가 발생했다.
Warrior 정보를 string으로 리턴하는데 System.Func`1[System.String] 가 출력되었고,
Inventory의 정보를 StringBuilder를 사용해서 작성하는데 뭔가 잘못되어서 스택 오버플로우가 발생했다.
월요일 날은 이것들을 수정하는 것 부터 작업을 시작해야할 것 같다.