01/04 본캠프#8

guno park·2024년 1월 4일
0

본캠프

목록 보기
9/77

WIL 관련

원래 오늘이 WIL 작성하기로 한 날이지만, 강의 과제들 다 끝내고 주말에 풀이랑 비교하면서 다른 점, 배운 점 등 정리해서 작성해보기로 했다. WIL 취지에는 그게 더 좋을 듯 하다.

4주차 과제 - 턴제 TextRPG 만들기

  • 목표 : 기본적인 턴 기반 RPG 게임을 만들어 봅시다.
  • 과제 요구 사항에 맞춰 하나하나 풀어가며 설명합니다.

과제 요구사항

ICharacter라는 인터페이스를 정의하세요.

  • 가져야하는 프로퍼티
    Name
    Health
    Attack
    IsDead
    TakeDamage(int damage)
interface ICharacter
    {
        string Name { get; set; }
        int Health { get; set; }
        int Attack { get; set; }
        bool IsDead { get; set; }
        void TakeDamage(int damage);        
    }

인터페이스는 추상적인 클래스(?) 같은 것으로 동작은 가지지만 동작의 구현은 가지지 않는다!
상기된 조건을 만족했으니 다음으로.

ICharacter를 구현하는 Warrior와 Monster라는 클래스 만들기

  • Warrior는 플레이어, Monster는 몬스터를 나타냅니다.
  • Monster 클래스에서 파생된 Goblin과 Dragon 이라는 클래스 추가하기
public class Warrior : ICharacter //플레이어 캐릭터
    {
        public string Name { get; set; }
        public int Health { get; set; }
        public int Attack { get; set; }
        public bool IsDead { get; set; }

        public Warrior() //생성자를 통해 생성될 때 기본 능력치 세팅
        {
            Name = "플레이어";
            Health = 100;
            Attack = 20;
            IsDead = false;
        }

        public void TakeDamage(int damage) //데미지를 받았을 때의 처리
        {
            Health -= damage;
            if (Health <= 0)
            {
                Health = 0;
                IsDead = true;
            }
        }
    }

    public class Monster : ICharacter
    {
        public string Name { get; set; }
        public int Health { get; set; }
        public int Attack { get; set; }
        public bool IsDead { get; set; }

        public void TakeDamage(int damage) 
        {
            Health -= damage;
            if (Health <= 0)
            {
                Health = 0;
                IsDead = true;
            }
        }
    }

    public class Goblin : Monster //클래스를 상속받기때문에 중복 내용은 작성 X
    {
        public Goblin() //생성자를 통해 생성될 때 기본 능력치 세팅
        {
            Name = "고블린";
            Health = 30;
            Attack = 5;
            IsDead = false;
        }
    }

    public class Dragon : Monster
    { 
        public Dragon() //생성자를 통해 생성될 때 기본 능력치 세팅
        {
            Name = "드래곤";
            Health = 200;
            Attack = 30;
            IsDead = false;
        }
    }

처음 작성할 때 평소 하던것처럼

public Monster Goblin{}

이라고 하니 당연하게도 작성이 안된다.
Goblin과 Dragon은 Monster클래스를 상속받아 클래스를 생성하는 것이지 Monster 클래스의 객체를 만드는게 아니라는 점.
차이점을 명확히 기억하자.

IItem 인터페이스 정의하기

  • 가져야할 프로퍼티
    Name
    Use(Warrior warrior) - 아이템을 사용하는 메서드
  • IItem을 구현하는 HealthPotion과 StrengthPostion 클래스 만들기
interface IItem
    {
        string Name { get; set; }
        void use(Warrior warrior);
    }
    

    public class HealthPotion : IItem
    {
        public string Name { get; set; }

        public HealthPotion()
        {
            Name = "회복 물약";
        }

        public void use(Warrior warrior)
        {
            warrior.Health += 50;
            Console.WriteLine("회복 물약을 사용하셨습니다. (+50)");
        }
    }


    public class StrengthPotion : IItem
    {
        public string Name { get; set; }

        public StrengthPotion() 
        {
            Name = "공격력 물약";
        }

        public void use(Warrior warrior)
        {
            warrior.Attack += 5;
            Console.WriteLine("공격력 물약을 사용하셨습니다. (+5)");
        }
    }

인터페이스를 받아서 두 가지 클래스의 물약을 만들고 물약별로 사용했을 때의 메서드를 작성했다.

Stage 클래스 만들기

  • 스테이지가 시작되면, 플레이어와 몬스터가 교대로 턴을 진행합니다.
  • 둘 중 하나가 죽으면 스테이지가 종료되고, 결과를 출력합니다.
  • 스테이지가 끝날 때, 플레이어가 살아있다면 보상 아이템 중 하나를 선택하여 사용할 수 있습니다. (회복 물약 or 힘 물약)
 public class Stage
    {                
    	//스테이지 클리어 보상용 아이템 인스턴스 생성
        HealthPotion healthPotion = new HealthPotion();
        StrengthPotion strengthPotion = new StrengthPotion();        
        
        public int turn { get; private set; } //다른데서 수정 못하게 막아놓음.
        public int stagecount { get; private set; }

        public Monster Start() //몬스터를 반환
        {            
            stagecount++;
            Console.WriteLine("스테이지 {0} 시작", stagecount);
            Console.WriteLine();
            return SpawnMonster(stagecount); //몬스터를 반환
        }


        public Monster SpawnMonster(int stagecount) //스테이지카운트를 파라미터로 받고, 몬스터를 반환함.
        {            
            if (stagecount % 10 ==0) //10턴마다 드래곤(보스) 등장
            {
                Dragon dragon = new Dragon();
                Console.WriteLine("드래곤이 나타났다.");
                Console.WriteLine();
                return dragon;
            }
            else
            {
                Goblin goblin = new Goblin();
                Console.WriteLine("고블린이 나타났다.");
                Console.WriteLine();
                return goblin;
            }       
        }


        public void Battle(Warrior warrior,Monster monster) //전투 메서드
        {
            turn++; 
            if (turn %2 != 0) //시작 턴 & 홀수 턴은 플레이어
            {
                Console.WriteLine("플레이어의 차례!");
                Console.WriteLine("플레이어가 {0}을 {1}의 데미지로 공격했다!",monster.Name,warrior.Attack);
                monster.TakeDamage(warrior.Attack);
                Console.WriteLine("{0}의 남은 체력 : {1}", monster.Name, monster.Health);
                Console.WriteLine();
            }
            else //그 외에는 몬스터의 턴
            {
                Console.WriteLine("{0}의 차례!",monster.Name);
                Console.WriteLine("{0}가 플레이어를 {1}의 데미지로 공격했다!", monster.Name, monster.Attack);
                warrior.TakeDamage(monster.Attack);
                Console.WriteLine("플레이어의 남은 체력 : {0}", warrior.Health);
                Console.WriteLine();
            }
        }


        public void ChooseItem(Warrior warrior) //전투에서 승리하면 나오는 메서드
        {
            turn = 0; //턴 초기화
            bool check = true; //1번 or 2번만 고르게하기

            while (check)
            {
                Console.WriteLine("전투에서 승리하셨습니다!");
                Console.WriteLine("보상을 선택해주세요. ");
                Console.WriteLine("1. 회복물약 (+50)   2. 공격력 물약 (+5) ");
                Console.WriteLine();

                switch (Console.ReadKey(true).Key)
                {
                    case ConsoleKey.NumPad1:
                        healthPotion.use(warrior);
                        check = false;
                        break;

                    case ConsoleKey.NumPad2:
                        strengthPotion.use(warrior);
                        check = false;
                        break;

                    default:
                        Console.WriteLine("잘못된 입력입니다.");
                        break;
                }
            }
        }
    }

Stage를 구현하면서 어려웠던 점은 Start() 메서드를 어떻게 구현할지 고민을 많이 했다.
정확하게는 몬스터를 어떤 방식으로 생성해야 하는가였는데, Main()에서 해도 될 법한 것들이긴 했지만, 앞에서 배운 내용들도 있고, Stage 클래스 안에서 다 끝내지겠다 싶어서 이렇게 작성해봤다.

SpawnMob()

이라는 메서드를 하나 만들어서 특정 스테이지에만 강한 몬스터인 드래곤이 나오게 해두고 그 외에는 고블린이 나오게 작성해주며 생성된 몬스터를 반환 해주었다.
그리고 Start()에서도 몬스터를 반환하며 Main() 함수에서 호출 했을 때 몬스터가 설정되도록 해두었다.

Main()

  • 각 스테이지가 시작할 때 플레이어와 몬스터의 상태를 출력해주세요.
  • 각 턴이 진행될 때 천천히 보여지도록 Thread.Sleep을 사용해서 1초의 대기시간을 추가해주세요.
static void Main(string[] args)
        {
            Stage stage = new Stage();
            Warrior warrior = new Warrior();

            while (true)
            {
            	//플레이어와 몬스터의 상태 출력
                Monster mob = stage.Start();
                Console.WriteLine("플레이어 | 체력 : {0}, 공격력 : {1}", warrior.Health, warrior.Attack);
                Console.WriteLine();
                Console.WriteLine("{0} | 체력 : {1}, 공격력 : {2}", mob.Name,mob.Health,mob.Attack);
                Console.WriteLine();
                Console.WriteLine("계속 진행하시려면 아무 키나 누르세요") ; //빨리 안넘어가게
                Console.WriteLine();
                Console.ReadKey(true);

                while (!warrior.IsDead && !mob.IsDead) //둘 다 살아있을때만
                {
                    stage.Battle(warrior, mob);
                    Thread.Sleep(1000);
                }

                if (warrior.IsDead) //플레이어가 죽으면?
                {
                    Console.WriteLine("플레이어는 죽고 말았습니다. {0}에게 말이죠.", mob.Name);
                    break;
                }
				//그 외에는 스테이지 클리어로 판정
                stage.ChooseItem(warrior);
                Thread.Sleep(1000);
                Console.Clear();
            }                
        }

이 부분을 작성하면서 while문 안에도 Thread.Sleep이 써진다는 걸 알아버렸다.

과제 후기

사실 턴제 알피지를 만들었다기 보다는, 추후 팀과제로 만들어야 될 부분을 미리보기 한 것 같은 느낌이다. 팀 과제 할 때는 몇가지 기능을 더 추가해서 넣어봐도 괜찮을 것 같다.

5주차 과제1 - 히스토그램 내 가장 큰 직사각형

과제 주소(leetcode) <뭔지 모르겠다면 한 번 보고오도록 하자.

출제자의 의도

너비가 같고, 높이가 다른 히스토그램 배열들 중에서, 직사각형을 그렸을 때 가장 큰 직사각형의 크기를 구하라

  • 제한사항
    1<= 높이 배열의 길이 <= 10^5
    0 <= 높이 <=10^4

설명

이것도 구획별로 뜯어보는게 설명하기가 편한데, 일단 내가 생각한 풀이 방법부터 적어본다.
앞에서부터 차례대로 진행했을 때

2를 기준으로 좌(는 없으니까) 우를 따져 직사각형을 계산하면 이런 모양이 나온다.


다음 1을 기준으로 좌우를 따졌을 때도 방금 전과 똑같은 모양이 나온다.


5를 기준으로 좌우를 따져 그렸을 때에도 왼쪽은 1이기 때문에 또 다시 겹치는 그림이 나온다.

그러므로 왼쪽에서부터 차례로 진행한다 했을 때(for문) 한 기둥의 오른쪽으로 얼마나(너비) 직사각형을 그릴 수 있는 지만 구한다면 해결되리라 판단했다.

코드 풀이

직사각형 넓이 구하기

  static int LargestRectangleArea(List<int> height)
 {
     int temp = 0; //반환할 최대 넓이
     List<int> width = new List<int>(); //너비를 받아줄 리스트를 생성            

     for (int i = 0; i < height.Count; i++)
     {
         width.Add(1); //너비의 최소단위는 1
         for (int j = i + 1; j < height.Count; j++)
         {
             if (height[i] <= height[j]) //자기 자신보다 크거나 같으면 너비++
             {
                 width[i]++;
             }
             else //작다면 더 이상 진행할 필요가 없음.
                 break;
         }
     }

     for (int i = 0; i < height.Count - 1; i++)
     {
         if ((width[i]) * height[i] <= (width[i + 1] ) * height[i + 1]) //받아온 너비 * 높이를 해서 최대 크기를 구한다.
             temp = (width[i + 1] + 1) * height[i + 1];
     }
     return temp;
 }

위에서 설명한 대로 for문을 이용해서 각 기둥이 가질 수 있는 최대 너비를 구해준다.
그 후 다시 너비와 높이를 이용해 직사각형의 최대 넓이는 구해 반환해준다.

Main()

static void Main(string[] args)
{
    List<int> height = new List<int>(); //입력받을 높이 리스트
    bool check = false;
    

    while (true)
    {
        Console.WriteLine($"{height.Count + 1}번째 직사각형의 높이를 입력하세요.");
        Console.WriteLine($"숫자 이외의 값을 입력하면 넘어갑니다.");
        string input = Console.ReadLine();
        int hei = 0;
        bool intcheck = int.TryParse(input, out hei);
        switch (intcheck && height.Count <= Math.Pow(10, 5)) //Math.Pow = n제곱
        {
            case true:
                {
                    if (hei < 1 || hei > Math.Pow(10, 4))
                        break;
                    else
                    {
                        height.Add(hei);
                    }
                }
                break;
            default:
                check = true;
                break;
        }
        if (check)
            break;
    }
    Console.WriteLine("주어진 배열에서 직사각형의 최대 크기는 {0}입니다.", LargestRectangleArea(height));
}

메인에는 간단하게 히스토그램에 넣을 값들을 입력받는 기능을 만들었다.
원래 문제 지문에는 배열을 사용하라고 되있었는데, 잘 되는지 체크해보려고 임의의 숫자를 입력해보고 싶어져서 이렇게 만들었다.

여기서 Math.Pow(x,y) 라는 함수가 나오는데 이 함수가 x^y 니까 잘 외워두자.
이런건 보통 많이 쓰드라

검증

간단한 검증을 위해 4가지 숫자를 입력해보았다. 더 늘리면 내가 계산하기 어려워서 4가지만 했다.
입력된 숫자는 [5,9,11,3]이다.

  • 작성한 알고리즘

  • 수작업

    얼추 맞는 것 같다.

작성 후기

이런 부류의 문제는 코드 작성 실력이 뛰어난 사람보다 머리를 잘 굴리는 사람이 빨리 푸는게 아닐까 싶다. 머리가 말랑말랑 해지는 것 같다.

ETC

문영오 매니저님과의 상담

  • 리팩토링하는 과정을 잘 남기자! (TIL 이런 방식)
    성실성 학습습득력 등 많은 게 리팩토링(하고 기록하는 습관)에 담겨있다.
  • 알고리즘은 어려운게 맞다. 깨부해라 (사실 유니티가면 잘 안쓴다고 하심)
  • TIL 많아보인다고 괜히 줄이려 하지말고, 천천히 틀을 잡아가보자.

개인과제 (요번에 제출하는 거)

개인과제 제출 하루 전날이라 조원들끼리 시연해보면서 얘기하고 있었는데 갑자기 코드가 이상해졌다.
Program.cs에 작성한 코드인데 그 cs가 null 이란다. 근데 처음에는 이런 건 본 적도 없고, 들어본 적도 없어서 코드만 이리저리 만지작하고 있었다.
주석 처리를 해도 그 부분에서 오류가 발생한다고 해서 2시간정도 끙끙대다가 김영호 튜터님께 질문드렸더니, 같이 살펴봐주셨다. 처음 보시는 코드라 뭐가 문제인지 요모조모 살펴 보시더니 그냥 프로젝트가 이상해졌다고 얘기해주셨다.
새 프로젝트에 옮겨서 하니까 잘 되더라. 제출 전에 경우의 수를 다 검사해보고 제출해보기도 했는데, 잘 되서 다행이였다.
아무튼 질문 드리면서 얻은 내용이 몇개 있다.

요약

  • F5 = 디버깅 했을 때 .cs 파일을 못 찾았다? -> 프로젝트 오류났다.
  • 코드에 오류가 있는데 한 눈에 못 찾을 땐? F9 브레이크 포인트를 적극 사용하자
  • Ctrl + g = 줄 옮기기 / Ctrl + ; = 솔루션 검색
  • 그리고 코드 짜고 실행할 때는 디버깅으로 실행하기

0개의 댓글