ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 20230829 - 일지
    C#/C# 미니실습 2023. 8. 30. 23:26

    과제 : 콘솔로 텍스트 게임 만들기.

     

    1. 몬스터, 아이템의 정보를 .csv 파일로 따로 관리하기

     

      랜덤하게 몬스터를 젠하는 메서드를 구현해야 했다.

      이전까지는 일일히 객체의 정보를 입력하여 관리하였는데, 이번에는 외부 파일로 정보를 관리하는 것이 더 효율적일 것 같아서, 시도해보았다. 

     

     A. 현재 파일의 디렉토리 얻기

     

    검색을 통해 다음과 같이 구현하였다.

    public static string localPath = Directory.GetParent(Path.GetFullPath(@"..\Data\MonsterData.csv")).Parent.Parent.Parent.Parent.ToString();

     

    Path.GetFullPath

     : 절대 경로 정보를 string 타입으로 반환

     

    public static string GetFullPath (string path);

     

    static void Main(string[] args)
    {
        var fullPath = Path.GetFullPath(@"..");
        Console.WriteLine($"fullPath의 타입 : {fullPath.GetType().Name}");
        Console.WriteLine($"fullPath : {fullPath}");
    
        var memoPath = Path.GetFullPath(@"..memoPad.cs");
        Console.WriteLine($"fullPath의 타입 : {memoPath.GetType().Name}");
        Console.WriteLine($"fullPath : {memoPath}");
    }

    >

     

    의 위치가 반환되길 기대했는데, bin 폴더 안의 파일의 경로를 반환하였다.

     

    일단은, .Parent로 상위폴더로 직접 이동하는 식으로 원하는 경로를 반환하려 하였다.

    Path.GetFullPath(@"..\Data\MonsterData.csv").Parent.Parent.Parent.Parent

    그런데, Path.GetFullPath(@"..\Data\MonsterData.csv")) 의 반환값은 string 타입이기 때문에, Parent를 써서 상위 디렉토리를 구하려 했는데, 타입이 맞지 않아 에러가 발생했다.

     

    Directory 클래스와 DirectoryInfo 클래스

     : Directory 클래스는 정적 메서드를 사용하여 문자열을 반환.

     : DirectoryInfo 클래스는 인스턴스화한 객체를 반환. 이 객체와 메서드를 통하여 디렉토리 관리 가능(ex. Delete())

     

     따라서, DirectoryInfo 클래스로 경로를 인스턴스화한 객체를 반환한 뒤, Parent 와 같은 메서드를 사용하여 Data 폴더를 갖고 있는 디렉토리까지 접근 가능하다.

     

    static void Main(string[] args)
    {
        DirectoryInfo currentDirInfo = new DirectoryInfo(Directory.GetCurrentDirectory());
        DirectoryInfo parentDirInfo = currentDirInfo.Parent.Parent.Parent.Parent;
    
        Console.WriteLine($"현재 디렉토리 (DirectoryInfo): {currentDirInfo.FullName}");
        Console.WriteLine($"부모*4 디렉토리 (parentDirInfo): {parentDirInfo.FullName}");
    }

    >

     

     원하는 디렉토리까지 접근한 후, ToString()으로 문자열로 반환.

     

    !! 수정 코드

    public class Pathes
    {
        public static DirectoryInfo currentPath = new DirectoryInfo(Directory.GetCurrentDirectory());
        public static string localPath = currentPath.Parent.Parent.Parent.Parent.ToString();
        
        //... 생략 ...
    }

     

     

    B. 각 csv 파일의 Path 얻기

    Path.Combine

      : 문자열 배열을 한 경로로 결합.

    public static string Combine (params string[] paths);

     

    public static string MonsterDataPath()
    {
        var dataPath = @"Data";
        var fileName = @"MonsterData.csv";
        var fullPath = Path.Combine(localPath, dataPath, fileName);
        return fullPath;
    }

     

     

    2. 몬스터의 정보를 클래스에 적용시키기.

     

     A. 클래스와 필드에 대한 고민

     

     작업을 하면서, 몬스터 클래스에 지정한 필드는 다음과 같다.

    internal class Monster : Character
    {
        public int Level { get; }
        public int Hp { get; set; }
        public int Atk { get; }
        public int Def { get; }
        public int Gold { get; }
        public int Exp { get; }
        public string Name { get; }
        public string Chrd { get; }
        int _critical;
    
        public Monster(string name, string chrd, int level)
        {
            Name = name;
            Chrd = chrd;
            Level = level;
            Atk = level * 3;
            Def = level * 2;
            Hp = level * 20;
        }
    }

     

    이렇게 하면, 다양한 종류의 몬스터를 인스턴스화하여도, 같은 레벨이라면, 공격력, 방어력, Hp 가 같아 몬스터의 특색이 없어진다고 생각하였다.

    static void Main(string[] args)
    {
        Monster StrongRabbit = new Monster("우람한 토끼", "짐승", 3);
        Monster WeakRabbit = new Monster("비실비실한 토끼", "짐승", 3);
    
        Console.WriteLine($"우람한 토끼의 공격력 : {StrongRabbit.Atk}");
        Console.WriteLine($"비실비실한 토끼의 공격력 : {WeakRabbit.Atk}");
    
        Console.WriteLine($"우람한 토끼의 방어력 : {StrongRabbit.Def}");
        Console.WriteLine($"비실비실한 토끼의 방어력 : {WeakRabbit.Def}");
    
        Console.WriteLine($"우람한 토끼의 체력 : {StrongRabbit.Hp}");
        Console.WriteLine($"비실비실한 토끼 체력 : {WeakRabbit.Hp}");
    }

    >

     

     

    그래서,  필드값을 컬럼으로 하여 각 몬스터의 정보를 csv 파일에 저장하고, 불러오려고 했다.

    Monster 클래스는 생성자의 파라미터로 level을 받고 있기 때문에, level별로 정보를 csv 파일에 저장하면, 동일 몬스터에 대한 정보가 많아져 관리하기 어렵다고 생각했다.

      => level이 주어지면, 계산가능한 정보를 csv 파일에 저장하기로 하였다. (ex. 공격력 -> 공격계수)

    이 때의 컬럼은 Monster 클래스의 필드와 달라지기 때문에, 컬럼과 동일한 필드를 지닌 MonsterInfo 클래스를 만들어 csv 파일의 정보를 MonsterInfo 인스턴스를 요소로 하는 배열로 관리하기로 하였다.

    public class MonsterInfo
    {
        public string Name { get; }
        public string Chrd { get; }
        public float MonAtkCoeff { get; }       // 공격계수
        public float MonDefCoeff { get; }       // 방어계수
        public int MonHPCoeff { get; }          // HP 계수
        public int Exp { get; }                 // 경험치 계수
        public int StageRank { get; }           // 스테이지별 등장하는 몬스터를 차별화하기 위해 추가한 인덱스
        public int DropGold { get; }            // 드롭하는 골드 계수
        
        public MonsterInfo(string name, string chrd, float monAtkCoeff, float monDefCoeff, int monHPCoeff, int exp, int dropGold, int stageRank)
        {
            Name = name;
            Chrd = chrd;
            MonAtkCoeff = monAtkCoeff;
            MonDefCoeff = monDefCoeff;
            MonHPCoeff = monHPCoeff;
            Exp = exp;
            DropGold = dropGold;
            StageRank = stageRank;
        }
        
        // ... 생략 ...

     

     

    그림의 csv 파일을 MonsterInfo 인스턴스를 요소로 하는 배열로 만든 코드는 다음과 같다.

    class MonsterInfo
    {
        //... 생략...
        
        
        public static MonsterInfo[] GetMonsterInfo()
        {
            MonsterInfo[] AllMonsters = new MonsterInfo[] { };
    
            string fullPath = Pathes.MonsterDataPath();
            string[] monsterData = File.ReadAllLines(fullPath, Encoding.UTF8);
            string[] fieldNames = monsterData[0].Split(',');           // 컬럼 부분(첫줄)은 MonsterInfo의 필드 이름을 지니고 있음.
    
            for (int monIdx = 1; monIdx < monsterData.Length; monIdx++)
            {
                string[] monsterEach = monsterData[monIdx].Split(',');
    
                string name = monsterEach[Array.IndexOf(fieldNames, "Name")];
                string chrd = monsterEach[Array.IndexOf(fieldNames, "Chrd")];
                float monatkcoeff = float.Parse(monsterEach[Array.IndexOf(fieldNames, "MonAtkCoeff")]);
                float mondefcoeff = float.Parse(monsterEach[Array.IndexOf(fieldNames, "MonDefCoeff")]);
                int monHPcoeff = int.Parse(monsterEach[Array.IndexOf(fieldNames, "MonHPCoeff")]);
                int exp = int.Parse(monsterEach[Array.IndexOf(fieldNames, "Exp")]);
                int dropGold = int.Parse(monsterEach[Array.IndexOf(fieldNames, "DropGold")]);
                // 몬스터 스테이지 조정
                int stageRank = int.Parse(monsterEach[Array.IndexOf(fieldNames, "StageRank")]);
    
                MonsterInfo monsterDict = new MonsterInfo(name, chrd, monatkcoeff, mondefcoeff, monHPcoeff, exp, dropGold, stageRank);
    
                Array.Resize(ref AllMonsters, AllMonsters.Length + 1);
                AllMonsters[monIdx - 1] = monsterDict;
            }
    
            return AllMonsters;
        }

     

    static 메서드

      : 클래스로부터 객체를 생성하지 않고 직접 호출하는 메서드.

     

    Monster 클래스에서 몬스터 인스턴스를 초기화 할 때, MonsterInfo 인스턴스를 요소로 하는 배열에서 해당 Monster의 이름(Monster.Name)을 검색하여, 동일한 이름(MonsterInfo.Name)을 가지고 있는 MonsterInfo를 가지고와, 파라미터로 주어진 level 과의 계산을 수행하여, 필요한 필드값을 입력할 계획

    => 외부 클래스에서 객체 생성 없이 가져와야 함

    => GetMonsterInfo()를 public static 으로 선언.

     

    File.ReadAllLines()

      : 텍스트 파일을 열고 파일의 모든 줄을 문자열 배열로 읽어 들인 다음 파일을 닫는 메서드

    public static string[] ReadAllLines (string path, System.Text.Encoding encoding);

     

      : Encoding이 맞지 않는 경우, 글자가 깨지는 현상이 발생하여, csv 파일을 UTF-8로 인코딩한 후, 메서드 파라미터에도 파일에 적용된 UTF8 을 입력함.

    string fullPath = Pathes.MonsterDataPath();
    string[] monsterData = File.ReadAllLines(fullPath, Encoding.UTF8);

     

    이후, Monster 클래스에  MonsterInfo.GetMonsterInfo() 메서드를 통하여, MonsterInfo 인스턴스를 요소로 하는 배열을 통해 생성자에서 필드값 초기화를 수행할 수 있게끔 생성자를 수정하였다.

    internal class Monster : Character
    {
        public int Level { get; }
        public int Exp { get; }
        public int Hp { get; set; }
        public int Atk { get; }
        public int Def { get; }
        public int Gold { get; }
        public string Name { get; }
        public string Chrd { get; }
        int _critical;
        public MonsterInfo[] MonsterInfoArray = MonsterInfo.GetMonsterInfo();
        
        public Monster(string name, string chrd, int level)
        {
            Level = level;
            Name = name;
            Chrd = chrd;
            MonsterInfo[] monsterInfo = MonsterInfoArray.Where(mon => mon.Name == name).ToArray(); 
            Atk = (int)(Level * monsterInfo[0].MonAtkCoeff);
            Def = (int)(Level * monsterInfo[0].MonDefCoeff);
            Hp = Level * monsterInfo[0].MonHPCoeff;
            Exp = Level * monsterInfo[0].Exp;
            Gold = Level * monsterInfo[0].DropGold;
            _critical = 20;
        }
        
        //... 생략....

     

     B. 활용

     

    이후, Dungeon 클래스에서 몬스터의 임의 소환 기능을 구현하는 메서드 MonsterGen() 에 활용할 수 있었다.

     

    class Dungeon
    {
        private int _stage;
        public int Stage
        {
            get { return _stage; }
            set { _stage = value; }
        }
        private int _round;
        public int Round
        {
            get { return _round; }
            set { _round = value; }
        }
        //... 생략 ...
        
        public Monster[] BossMonsterGen()
        {
            Random random = new Random();
            int bossMonsterLevel = random.Next(Stage, Stage + 4);
            
            // MonsterInfo.GetMonsterInfo()를 통해 모든 몬스터의 MonsterInfo를 요소로 하는 배열 생성
            // Where을 통해 MonsterInfo.StageRank 와 현재 Dungeon.Stage를 비교하여 배열 제약
            MonsterInfo[] bossMonsterInfo = (Stage < 4) ? MonsterInfo.GetMonsterInfo().Where(mon => mon.StageRank < 0 && mon.StageRank > -Stage - 1).ToArray() : MonsterInfo.GetMonsterInfo().Where(mon => mon.StageRank == -Stage).ToArray();
            int bossIdx = random.Next(0, bossMonsterInfo.Length);
            
            // MonsterInfo에서 Name, Chrd, 해당 메서드에서 level을 파라미터로 새로운 Monster 클래스의 인스턴스 초기화.
            Monster bossMonster = new Monster(bossMonsterInfo[bossIdx].Name, bossMonsterInfo[bossIdx].Chrd, bossMonsterLevel);
            Monster[] getMonsterArray = new Monster[1];
            getMonsterArray[0] = bossMonster;
    
            return getMonsterArray;
        }

     

     

     

Designed by Tistory.