20230829 - 일지
과제 : 콘솔로 텍스트 게임 만들기.
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;
}