TextRPG 개인과제를 팀과제로 구현하기로 했다.

아직 개인학습이 끝나지 않은 분들도 계셔서 간단한 사항만 결정하고 금요일에 게임 컨셉을 정하고 역할 분담을 하기로 했다.

 

<회의 사항>

스크럼을 오전, 오후 2회로 늘리고 개인학습으로 대화를 많이 하지 않아서 친해지는 시간을 갖기로 했다.

맥 유저와 윈도우 유저 확인하고 발표와 영상 등을 정했다. 깃을 써보지 않은 분들이 많아서 목요일 저녁에 한 번씩 커밋 연습해보는 시간을 갖는 것이 좋을 것 같다.

 

게임 방향성은 팀원분이 추천해주신 참고 할 만한 게임인 다크드래곤을 찾아보고, TRPG 형식으로 주사위를 굴려 공격하는 형식은 어떨까 생각 중이다.

<오늘의 계획>

1. 기능 수정

2. 콘솔 꾸미기

 

  • 기능 수정

인트로 멘트가 한 글자씩 출력되면 좋을 것 같아서 while문과 Thread.Sleep()을 통해 0.05초에 한 글자씩 나오도록 기본설정을 하고, 아무 키를 입력했을 때 스킵되도록 했다.Console.KeyAvailable은 bool값으로 어떤 키를 눌렀을 때 true를 출력한다. 이를 이용하여 값이 true가 되면 Thread.Sleep()의 속도를 다시 0으로 만들어주어 전체 멘트가 나오도록 했다.이 기능은 자주 사용할 것 같아서 메서드로 만들어주었다.

// 한글자씩 출력
static void OutputTxt(string txt) // string = Char(문자) 배열
{
    Console.ForegroundColor = ConsoleColor.Cyan;

    // 아무 키나 눌렀을 때 스킵
    int speed = 50;
    int txtCount = 0; // 글자수
    while (txtCount != txt.Length) // txt 글자수가 끝까지 나올 때까지
    {
        if (Console.KeyAvailable) // 아무 키나 눌렸을 때 true
        {
            speed = 0;
            Console.ReadKey(true);
        }
        Console.Write(txt[txtCount]); // 글자를 하나하나 가져올 인덱스
        Thread.Sleep(speed);
        txtCount++;
    }
}

// 적용
static void DisplayGameIntro()
{
	string startTxt = "스파르타 마을에 오신 여러분 환영합니다.\n이곳에서 던전으로 들어가기 전 활동을 할 수 있습니다.\n";
	OutputTxt(startTxt);
}

Console.Readkey( ) : 사용자가 눌린 키 한 문자 정보를 리턴하는 메소드

Console.KeyAvailable : 키 입력값의 참거짓 여부

 

  • 콘솔 꾸미기

콘솔 꾸미기 명령어

// 콘솔 배경
Console.BackgroundColor = ConsoleColor.색상;
Console.Clear(); // 화면 지우기

// 글씨색 변경
Console.ForegroundColor = ConsoleColor.색상;

// 글씨색 기본색으로 되돌리기
Console.ResetColor();

// 콘솔창 이름 설정
Console.Title = "콘솔창";

 

 

<추가>

- 코드에 필요한 클래스를 별도의 파일로 만들고 분리해보기

- Readme 작성하기

 

<마치며>

C#에 좀 더 친숙해지는 것에 중점을 맞췄어야했는데 많은 기능을 구현하고 싶은 욕심에 새로운 메서드를 찾아보고 탐구하는 것에 너무 시간을 할애했던 것 같다. 잘하는 분들은 시간이 남았기 때문에 여러 가지 추가기능을 구현했음을 항상 유의하고 초보자인 나는 문법 위주로 공부해야겠다.

 

 

<구상 및 설계>

기본적으로 게임 시작화면과 선택지를 눌렀을 때, 캐릭터의 상태를 볼 수 있는 상태 보기창과 인벤토리 창으로 구성할 예정이다.

능력이 된다면 던전까지 구현하고 싶지만 시간과 능력 부족으로 팀 프로젝트 때 함께 구현해야할 것 같다.

 

게임 시작

- 1. 상태 보기

- 2. 인벤토리 - 1. 장착 관리

- 3. 상점

- 4. 던전 입장

 

<구현>

게임 시작

// 선택지
static int CheckValidInput(int min, int max)
{
    while (true)
    {
        string input = Console.ReadLine();

        bool parseSuccess = int.TryParse(input, out var ret);
        if (parseSuccess)
        {
            if (ret >= min && ret <= max)
                return ret;
        }

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

// 메인화면
static void GameLogo()
{
    Console.WriteLine("= Sparta Dungeon =");
    Console.WriteLine("1. 게임시작");
    Console.WriteLine("0. 나가기");
    Console.WriteLine();
    Console.WriteLine("원하시는 행동을 입력해주세요.");
    Console.Write(">> ");

    int input = CheckValidInput(0, 1);
    switch (input)
    {
        case 0:
            break;
        case 1:
            DisplayGameIntro();
            break;
    }
}

// 스타트
static void DisplayGameIntro()
{
    Console.Clear();
    Console.WriteLine("==================================================");
    Console.WriteLine("스파르타 마을에 오신 여러분 환영합니다.\n이곳에서 던전으로 들어가기 전 활동을 할 수 있습니다.");
    Console.WriteLine("==================================================");
    Console.WriteLine();
    Console.WriteLine("1. 상태 보기\n2. 인벤토리\n0. 메인화면");
    Console.WriteLine();
    Console.WriteLine("원하시는 행동을 입력해주세요.");
    Console.Write(">> ");

    int input = CheckValidInput(0, 2);
    switch (input)
    {
        case 0:
            Console.Beep();
            Main();
            break;
        case 1:
            DisplayMyInfo();
            break;
        case 2:
            DisplayInventory();
            break;
    }
}

 

상태 보기

class Program
{
	private static Character player;
    
    // 메인
    static void Main(string[] args)
    {
        GameDataSetting();
        DisplayGameIntro();
    }
    
    // 각종 데이터
    static void GameDataSetting()
    {
        // 캐릭터 정보 세팅
        player = new Character("Chad", "전사", 1, 10, 5, 100, 1500);

        // 아이템 정보 세팅
    }

    static void DisplayMyInfo()
    {
        Console.Clear();
        Console.Title = "= Information =";

        ChooseTextColor("= 상태 보기 =");
        LineTextColor("캐릭터의 정보가 표시됩니다.");
        Console.WriteLine();
        StatTextColor("Lv. ", player.Level.ToString("00")); // 00, 07 등 한자릿수도 두자릿수로 표현하기 위해 string 타입으로 변환
        Console.WriteLine();
        Console.WriteLine("{0} ( {1} )", player.Name, player.Job);
        StatTextColor("공격력 : ", player.Atk.ToString());
        Console.WriteLine();
        StatTextColor("방어력 : ", player.Def.ToString());
        Console.WriteLine();
        StatTextColor("체 력 : ", player.Hp.ToString());
        Console.WriteLine();
        StatTextColor("Gold : ", player.Gold.ToString());
        Console.WriteLine();
        Console.WriteLine();
        ChooseTextColor("0. 나가기");
        Console.WriteLine();
        Console.WriteLine("원하시는 행동을 입력해주세요.");
        Console.Write(">> ");

        int input = CheckValidInput(0, 0);
        switch (input)
        {
            case 0:
                DisplayGameIntro();
                break;
        }
    }
}

// 플레이어의 캐릭터 상태 클래스
public class Character
{
    public string Name { get; }
    public string Job { get; }
    public int Level { get; }
    public int Atk { get; }
    public int Def { get; }
    public int Hp { get; }
    public int Gold { get; }

    public Character(string name, string job, int level, int atk, int def, int hp, int gold)
    {
        Name = name;
        Job = job;
        Level = level;
        Atk = atk;
        Def = def;
        Hp = hp;
        Gold = gold;
    }
}

 

인벤토리

class Program
{
	private static Character player;
	private static Item[] items;
    
    // 각종 데이터
    static void GameDataSetting()
    {
        // 캐릭터 정보 세팅
        player = new Character("Chad", "전사", 1, 10, 5, 100, 1500);

        // 아이템 정보 세팅
        items = new Item[10]; // 최대 아이템 10개
        AddItem(new Item("무쇠갑옷", "무쇠로 만들어져 튼튼한 갑옷입니다.", 0, 0, 5, 0));
        AddItem(new Item("낡은 검", "쉽게 볼 수 있는 낡은 검입니다.", 1, 2, 0, 0));
        AddItem(new Item("고양이 수염", "고양이 수염은 행운을 가져다 줍니다. 야옹!", 1, 7, 7, 7));
    }

    // 아이템 추가
    static void AddItem(Item item)
    {
        if (Item.ItemCount == 10) return; // 아이템이 꽉차면 아무일도 일어나지 않는다
        items[Item.ItemCount] = item; // 0개 -> items[0], 1개 -> items[1] ...
        Item.ItemCount++; // AddItem이 될 때마다 ItemCount가 올라간다
    }

    // 인벤토리
    static void DisplayInventory()
    {
        Console.Clear();
        Console.Title = "= inventory =";

        Console.WriteLine("인벤토리");
        Console.WriteLine("보유 중인 아이템을 관리할 수 있습니다.");
        Console.WriteLine();
        Console.WriteLine("[아이템 목록]");

        for (int i = 0; i < Item.ItemCount; i++)
        {
            items[i].ItemStat();
        }

        Console.WriteLine("");
        Console.WriteLine("1. 장착 관리\n0. 나가기");
        Console.WriteLine();
        Console.WriteLine("원하시는 행동을 입력해주세요.");
        Console.Write(">> ");

        int input = CheckValidInput(0, 1);
        switch (input)
        {
            case 0:
                DisplayGameIntro();
                break;
            case 1:
                DisplayerEquip();
                break;
        }
    }
}

// 아이템 클래스
public class Item
{
    public string Name { get; }
    public string Description { get; }
    public int Type { get; }
    public int AtkOption { get; }
    public int DefOption { get; }
    public int HpOption { get; }
    public bool IsEquipped { get; set; }

    public static int ItemCount = 0; // static을 붙임으로 Item이라는 클래스에 귀속된다

    public Item(string name, string description, int type, int atkOption, int defOption, int hpOption, bool isEquipped = false)
    {
        Name = name;
        Description = description;
        Type = type;
        AtkOption = atkOption;
        DefOption = defOption;
        HpOption = hpOption;
        IsEquipped = isEquipped;
    }
	
    // 아이템 착용여부
    public void ItemStat(bool wearItem = false, int index = 0)
    {
        Console.Write("- ");
        if (IsEquipped)
        {
            Console.Write("[");
            Console.Write("E");
            Console.Write("]");
        }
        Console.Write(Name);
        Console.Write(" | ");

        if (AtkOption != 0) Console.Write($"AtkOption {(AtkOption >= 0 ? "+" : "")}{AtkOption} "); // 삼항연산자
        if (DefOption != 0) Console.Write($"DefOption {(DefOption >= 0 ? "+" : "")}{DefOption} "); // 옵션이 0이 아니라면 옵션수치를 내보내라
        if (HpOption != 0) Console.Write($"HpOption {(HpOption >= 0 ? "+" : "")}{HpOption} "); // [조건 ? 참 : 거짓]

        Console.Write(" | ");

        Console.WriteLine(Description);
    }
}

 

장착관리

// 장착 관리
static void DisplayerEquip()
{
    Console.Clear();
    Console.Title = "= Item Equip =";

    Console.WriteLine("인벤토리 - 장착 관리");
    Console.WriteLine("보유 중인 아이템을 관리할 수 있습니다.");
    Console.WriteLine();
    Console.WriteLine("[아이템 목록]");
    for (int i = 0; i < Item.ItemCount; i++)
    {
        items[i].ItemStat(true, i + 1);
    }
    Console.WriteLine();
    Console.WriteLine("0. 나가기");
    Console.WriteLine();
    Console.WriteLine("원하시는 행동을 입력해주세요.");
    Console.Write(">> ");

    int input = CheckValidInput(0, Item.ItemCount);
    switch (input)
    {
        case 0:
            DisplayInventory();
            break;
        default:
            ToggleEquipStatus(input - 1); // 유저가 입력하는건 1, 2, 3...  실제 배열에는 0, 1, 2...
            DisplayerEquip();
            break;
    }
}

// 장비 장착 여부
private static void ToggleEquipStatus(int index)
{
    items[index].IsEquipped = !items[index].IsEquipped; // ! : 불형의 변수를 반대로 만들어주는 것
}

// 장비 능력치 더하기
private static int itemStatSumAtk()
{
    int sum = 0;
    for (int i = 0; i < Item.ItemCount; i++)
    {
        if (items[i].IsEquipped) sum += items[i].AtkOption;
    }
    return sum;
}
private static int itemStatSumDef()
{
    int sum = 0;
    for (int i = 0; i < Item.ItemCount; i++)
    {
        if (items[i].IsEquipped) sum += items[i].DefOption;
    }
    return sum;
}
private static int itemStatSumHp()
{
    int sum = 0;
    for (int i = 0; i < Item.ItemCount; i++)
    {
        if (items[i].IsEquipped) sum += items[i].HpOption;
    }
    return sum;
}

 

 

<C#에서 다중 상속을 사용하지 않는 이유>

  • 다이아몬드 문제

다중 상속을 사용하면 한 개의 클래스가 두 개 이상의 부모 클래스로부터 동일한 맴버를 상속받는다.

A라는 클래스를 B와 C가 상속받는다고 해보자.

D라는 클래스가 B와 C를 상속받게 된다면 결국엔 A를 상속받은 것과 같게 된다. 이것을 다이아몬드 모양으로 상속받는다고 한다. 다이아몬드로 B가 상속받은 A 클래스와 C가 상속받은 A가 동일함으로써 어떤 부모 클래스의 맴버를 사용해야할지 모호해지는 문제가 생겨난다.

  • 설계의 복잡성 증가

다중 상속을 하게 되면 어떤 클래스로부터 어떤 맴버를 상속을 받아야할지 상속 관계를 파악하기 어려워지며, 클래스 간의 관계가 복잡해진다.

  • 이름 충돌과 충돌 해결의 어려움

마찬가지로 같은 변수명이 있을 수 있고 그로 인해 중첩이 되어 사용할 수 없는 경우가 나타날 수 있다.

  • 일관성과 단순성 유지

그로 인해 C#은 일관성과 단순성을 유지하고 있다.

 

<인터페이스를 사용하는 이유>

  • 코드의 재사용성

우리는 변수를 쓰다보니 조금 더 함축해서 다양하게 쓰고 싶어 배열을 사용하였고, 코드들에 대한 일련 묶음들을 재사용 하고 싶기 때문에 메서드를 만들었고, 메서드나 필드를 한 곳에 뭉쳐 사용하고 싶기 때문에 Struct나 Class를 만들었다. 이처럼 동일한 코드들을 계속 재구현하지 않고 재사용하기 위해 인터페이스를 사용한다.

  • 다중 상속 제공
  • 유연한 설계

인터페이스는 구현을 제시할 뿐 실제 구현은 클래스가 하기 때문에 구현부와 제시부가 연관되지 않아도 된다.

 

 

인터페이스(Interface)

클래스가 구현해야하는 맴버들을 정의하는 것. 클래스에 대한 제약 조건을 건다.

클래스가 인터페이스를 구현할 경우, 모든 인터페이스 맴버들을 구현해야하며, 다중 상속을 지원한다.

 

인터페이스 구현

인터페이스를 구현할 때는 interface라는 키보드를 사용한다. 대표적인 규칙으로 이름이 I(대문자i)로 시작한다.

// 아이템을 사용할 수 있는 인터페이스 정의
public interface IUsable
{
    void Use(); // 사용 메서드 선언
}
// 인터페이스를 구현하는 클래스 생성
// 아이템 클래스
public class Item : IUsable
{
    public string Name { get; set; } // 자동 프로퍼티와 필드 역할을 같이 한다

    public void Use()
    {
        Console.WriteLine("아이템 {0}을 사용했습니다.", Name);
    }
}

// 플레이어 클래스
public class Player
{
    public void UseItem(IUsable item)
    {
        item.Use();
    }
}
// 게임 실행
static void Main()
{
    Player player = new Player();
    Item item = new Item { Name = "Health Potion" };
    player.UseItem(item);
}

 

다중상속

// 인터페이스 1
public interface IItemPickable
{
    void PickUp();
}

// 인터페이스 2
public interface IDroppable
{
    void Drop();
}

// 다중상속
// 아이템 클래스
public class Item : IItemPickable, IDroppable
{
    public string Name { get; set; }

    public void PickUp()
    {
        Console.WriteLine("아이템 {0}을 주웠습니다.", Name);
    }

    public void Drop()
    {
        Console.WriteLine("아이템 {0}을 버렸습니다.", Name);
    }
}

// 플레이어 클래스
public class Player
{
    public void InteractWithItem(IItemPickable item)
    {
        item.PickUp();
    }

    public void DropItem(IDroppable item)
    {
        item.Drop();
    }
}

// 게임 실행
static void Main()
{
    Player player = new Player();
    Item item = new Item { Name = "Sword" };

    // 아이템 주울 수 있음
    player.InteractWithItem(item);

    // 아이템 버릴 수 있음
    player.DropItem(item);
}

 

<인터페이스와 추상클래스>

인터페이스

- 추상적인 동작만 정의하고 구현을 아예 갖지 않는다.

- 클래스가 아니며, 다중 상속이 가능하다.

- 클래스 간의 결합도를 낮추고, 유연한 상호작용이 가능하다.

- 코드의 재사용성과 확장성을 향상시킨다.

- 인터페이스를 구현하는 클래스가 모든 동작을 구현해야하기 때문에 작업량이 많다.

 

 

추상클래스

- 일부 동작의 구현을 가지고 구현을 할 수 있다.

- 단일상속만 가능하다.

- 공통된 동작을 추상화하여 코드의 중복을 방지하고 확장성을 제공한다.

- 구현된 동작을 가지고 있어 하위 클래스에서 재정의하지 않아도 될 때 유용하다.

- 다중 상속이 불가능하고, 상속으로 밀접하게 결합된 클래스로 유연성이 제한된다.

 

 

열거형 (Enums)

서로 연관된 상수들의 집합. 열거형의 상수 값 은 정수 값이다.

- 가독성
- 자기 문서화
- switch문과 호완성

 

열거형 구현

// 열거형 정의
public enum Month
{
    January = 1,
    February,
    March,
    April,
    May,
    June,
    July,
    August,
    September,
    October,
    November,
    December
}

internal class Program
{
    static void Main(string[] args)
    {
        // 처리하는 함수
        static void ProcessMonth(int month)
        {
            if (month >= (int)Month.January && month <= (int)Month.December)
            {
                Month selectedMonth = (Month)month;
                Console.WriteLine("선택한 월은 {0}입니다.", selectedMonth);
                // 월에 따른 처리 로직 추가
            }
            else
            {
                Console.WriteLine("올바른 월을 입력해주세요.");
            }
        }

        // 실행
        int userInput = 7; // 사용자 입력 예시
        ProcessMonth(userInput);
    }
}

 

 

+ Recent posts