TIL(Today I Learned)

텍스트 RPG 게임 만들기 (3), 알고리즘 기초 - TIL#10

Najdorf 2024. 1. 5. 21:38
728x90

어제 만들던 것에 이어서 남은 필수 요구사항을 구현하기로 했다.

  • 상점에서 구매 시 골드가 부족한 경우 처리
  • 장비해서 변화된 능력치 상태창에 표시
  • InputAction에서 의도된 입력 외의 입력 처리
  • 아이템 종류 더 추가하기

https://sicilian-najdorf.tistory.com/11

 

텍스트 RPG 게임 만들기 (2) - TIL#9

오늘은 어제까지 했던 것과 다르게 C# 문법에 대해 공부를 따로 하지는 않고 4주차 과제인 '텍스트 RPG 게임 만들기'의 필수 기능을 구현을 최대한 하기 위한 시간을 가지도록 했다. 지난번에 코드

sicilian-najdorf.tistory.com


우선, 상점에서 골드가 부족할 때 아이템 구매를 하려고 하면

"Gold가 부족합니다!" 라는 메시지를 띄우려 한다.

int select = choice - 1;

if (itemsArr[select].isBought == false)
{
    if (player.gold >= itemsArr[select].reqGold)
    {
        player.AddInventory(select);
        player.PayGold(itemsArr[select].reqGold);
        itemsArr[select].isBought = true;
        Console.WriteLine("구매를 완료했습니다!");
    }
    else
    {
        Console.WriteLine("Gold가 부족합니다!");
    }
}
else
{
    Console.WriteLine("이미 구매한 아이템입니다.");
}

 

플레이어가 '인덱스 + 1' 만큼의 값을 입력하면(표시되는 아이템은 1부터 시작하기 때문에),

입력된 값에 다시 -1을 해 인덱스 값과 동일하게 만들어주고,

 

해당 인덱스의 아이템이 구매되지 않았으면(isBought가 false면),

보유 골드가 필요 골드보다 많은 가 체크(gold가 reqGold보다 크거나 같으면),

인벤토리에 할당하고 구매 완료를 변수에 할당해준다.

 

그렇지 않으면

"Gold가 부족합니다!"를 콘솔창에 띄운다.

 

 

상태창에 장비한 능력치를 표시하는 건

어떻게 구현해야 할 지 살짝 고민했는데,

의외로 짜고보니 별 거 없던 기능이었다.

        int equipAtk = player.atk - player.initAtk;
        int equipDef = player.def - player.initDef;
        string addAtk = "";
        string addDef = "";

        if (equipAtk != 0)
        {
            addAtk = " (+" + equipAtk + ")";
        }

        if (equipDef != 0)
        {
            addDef = " (+" + equipDef + ")";
        }

        Console.Write("상태 보기\n캐릭터의 정보가 표시됩니다.\n\n");
        Console.WriteLine("Lv. " + player.level);
        Console.WriteLine("{0} ( {1} )", player.name, player.job);
        Console.WriteLine("공격력 : " + player.atk + addAtk);
        Console.WriteLine("방어력 : " + player.def + addDef);

 

Player 클래스에 초기 공격력과 방어력을 저장하는 변수

initAtk, initDef를 정의하고 초기화해준다.

 

그리고 (현재 능력치 - 초기 능력치) 를 계산해

장비로 인해 증가된 능력치 값을 계산하고,

 

이게 0이 아니라면 추가로 출력하는 방식이다.

 

정말 별 거 없는 로직이지만,

한 가지 내심 찝찝했던 점은,

 

이렇게 능력치를 만들 때 마다 공격력/방어력을 따로 만들어

변수를 2개씩 만들고 같은 작업을 2번씩 할 거였으면,

 

굳이 아이템 정보에 isWeapon이라는 공격력/방어력 판단용

Boolean 값을 만들어야 했나 싶다.

 

살짝 비효율적이다라는 생각도 들고...

어쩌면 코드를 짜는 데 있어 더 좋은 방법이 있을지도 모르겠다.

 

 

다음은 InputAction의 의도되지 않은 예외 처리이다.

요것도 사실은 Parse 대신에 TryParse 메서드를 사용하면

비교적 간단하게 끝날 것을 알고 있었다.

public int InputAction()
{
    int num = 0;
    while (true)
    {
        Console.Write("원하시는 행동을 입력해주세요.\n>>");
        string input = Console.ReadLine();
        bool isInt;
        isInt = int.TryParse(input, out num);

        if (isInt)
        {
            break;
        }
        else
        {
            Console.WriteLine("잘못된 값입니다. 화면에 표시된 정수를 입력해주세요.");
        }
    }
    return num;
}

 

처음에는 isInt 값을 기준으로

if 문으로 분기해야 하나? 싶어서 그렇게 만들었더니

 

메서드의 반환값이 일부 조건에서만 반영된다고

비주얼 스튜디오가 손사레를 쳐서...

 

조금 손봤더니 이번엔 기능 재시작이 문제였다.

의도되지 않은 문자열을 받고 나면?

그대로 끝낼 수도 없고 올바른 입력을 받을 때까지 시도해야 할텐데,

 

재귀함수를 써야 하나? 싶어서 썼더니

코드가 매우 더러워졌다...

 

 

답은 반복문이었다.

반복문은 이럴 때 쓰라고 있는 것이었다!!

 

굳이 if 문, 재귀함수 여럿 써서 복잡하게 할 필요 없이

정상적인 값이 들어오면 break를 해서 값을 반환하면 끝나는 것이었다!!

 

 

근데 살짝 마음에 안 드는 건..,

여러 번 잘못 입력할 때마다 콘솔창에 메시지가 쌓여 간다는 것이다.

 

미관상 보기 그렇지만 기능상으론 문제가 없으니,

나중에 개선하기로 했다.

 

 

나만의 새로운 아이템 추가하기

        itemsArr[6].name = "개쩌는 엑스칼리버";
        itemsArr[6].comment = "끝판왕.";
        itemsArr[6].point = 999;
        itemsArr[6].reqGold = 9999;
        itemsArr[6].isWeapon = true;

 

이건 코드를 설명할 필요도 없이

남은 인덱스에 값을 지정만 해주면 되는거라...

 

개사기 아이템을 하나 만들어 넣기로 했다.

(대신 개비쌈)

 

이 아이템을 보유한 플레이어는

훗날 게임이 너무 쉬워져서 흥미를 못 느낄 것 같다. (사실상 사는게 엔딩)

 

 

 

이렇게 텍스트 RPG 필수 요구조건들을 모두 구현했고,

작동 테스트하니 버그 없이 잘 돌아가는 모습을 보여줬다.

(중간에 인벤토리에서 상점에서 안 산 아이템을 장착할 수 있는 버그가 있었지만 수정함)

 

해당 프로젝트의 Github 링크를 남기니 궁금하면 봐도 괜찮을 것 같다.

(남은 선택 요구사항도 만들어 볼 예정)

https://github.com/cn7249/TextRPG

 

GitHub - cn7249/TextRPG: 개인 과제 - 텍스트 RPG 게임 만들기

개인 과제 - 텍스트 RPG 게임 만들기. Contribute to cn7249/TextRPG development by creating an account on GitHub.

github.com

 

 


알고리즘(Algorithm).

문제를 해결하기 위한 명확한 절차 또는 방법이다.

 

어떤 목적에 맞게 입력을 받아 원하는 출력을

정확하고 일관되게 제공을 해야하는 것이다.

 

입력과 출력을 일관되게 처리한다는 점에서

컴퓨터 프로그래밍에서 매우 중요하게 다뤄지고,

 

좋은 알고리즘은 당연히 효율적인 것이다.

 

 

 

알고리즘이 효율적일 수록 더 적은 자원(시간, 메모리)을 사용해

의도된 결과를 낼 수 있기 때문에 효율적인 것은 상당히 중요하다.

 

그래서 알고리즘의 효율성을 나타낼 수 있는 방법이 있다.

 

'Big O 표기법' 이라는 것인데,

입력의 크기에 따라 알고리즘이 얼마나 많은 시간이나 공간을 필요로 하는가?

를 표기하는 방법이라고 이해하면 된다.

 

Big O 표기법은 알고리즘 성능의 최악의 경우를 반영을 해서

알고리즘의 효율성을 보수적으로 나타낸다는 특징이 있다.

 

Big O 표기법은 다음과 같이 나타낼 수 있다.

ex) O(1), O(n), O(n^2), O(log n)

 

 

빅오 표기법의 계산에는 다음과 같은 특징이 있다.

  • 상수 버리기
  • 최고 차수 항목만 남기기
  • 다항식의 경우 최고 차수 항목만 고려
  • 연산자 상수 무시

조금 수학적인 용어들이 등장해서 어렵게 느껴지는 감이 있지만,

요약하자면, '가장 영향이 큰 것을 중심으로 쓰인다' 라는 것이다.

 

예를 들어, O(4n^3 + 2n^2 + 8n + 3)의 경우,

가장 큰 영향을 주는 n^3만 고려하고 나머지는 버린다.

따라서 간소화된 형태는 O(n^3)이 된다.

 

 

 

이렇게 알고리즘의 효율성에 대해 평가할 수 있는

빅오 표기법은 크게 시간 복잡도공간 복잡도를 나타내게 된다.

 

 

시간 복잡도(Time Complexity)

알고리즘이 얼마나 시간이 걸리는 지를 나타내고,

 

공간 복잡도(Space Complexity)

알고리즘이 얼마나 많은 메모리 공간을 차지하는 지를 나타낸다.

 

두 개념 모두 주의해야 할 점으론

실제 걸리는 시간(초)나 실제 메모리 크기(바이트)로 측정하는 것이 아니라는 점이다.

 

 

예컨데, n개의 인덱스를 갖는 배열의 최대값을 찾는 다음 예제를 보면...

int FindMax(int[] arr)
{
    for (int i = 0; i < arr.Length; i++)
    {
        bool isMax = true;

        for (int j = 0; j < arr.Length; j++)
        {
            if (arr[j] > arr[i])
            {
                isMax = false;
                break;
            }
        }

        if (isMax)
        {
            return arr[i];
        }
    }

    return -1;
}

int[] arr = new int[] { 1, 2, 3, 4, 5 };
int max = FindMax(arr);

 

시간 복잡도의 경우 두 개의 반복문이 n번씩 실행되고 있으므로

n * n = n^2,

즉, O(n^2)이 된다.

 

반면에, 공간 복잡도의 경우 별도의 메모리 공간을 사용하지 않기 때문에

O(1)이 된다.

 

 

 

시간 복잡도는 확실히 이해가 됐는데,

공간 복잡도는 다시 곱씹어 볼 필요가 있다고 생각한다.

728x90