[C#] Project. OOP_3

Lingtea_luv·2025년 4월 10일

Project

목록 보기
7/38
post-thumbnail

Project.OOP [完]


한 달 간의 수업을 바탕으로 이루어진 이번 프로젝트는 3일의 기간동안 진행됐다. 어떻게 하면 시간을 알차게 사용할 수 있는지 생각했고, 오직 소스코드에만 집중하고 싶어 레퍼런스 게임을 빠르게 알아보았다.

Reference Game

3일이라는 기간동안 대략적인 스토리, 시스템까지 구상하고 이를 구현하는 것은 거의 불가능했기에 내가 알고 있던 게임을 구현하고자 마음을 먹었고, 스팀에 들어가서 라이브러리와 인기 순위를 훑어보았다.

그렇게 눈에 들어온 것이 바로 "도박묵시록 다구리" 라는 게임인데, 블랙잭 홀짝 등 다양한 미니 게임으로 돈을 불려 빚을 탕감하는 게임이다. 다구리를 선택한 이유는 게임 내의 다양한 미니 게임이 존재하여 짧은 기간동안 기능 구현하는 것에 적합하다고 생각했고, 시스템과 스토리가 단순한 것에 비해 정말 재밌고 매력적인 점이 좋았기 때문이다.

레퍼런스 게임을 정했으니 어떤 기능을 구현할 것인지 정해야했는데, 많은 후보들이 있었지만 결정한 것은 강화 시스템, 코인 트레이드, 슬롯 머신, 블랙잭, 복권, 홀짝이다. 물론 1개 구현하는데에도 얼마의 시간이 걸릴지 짐작이 되지 않았기에 6개를 모두 구현할 수 있을 것이라고 생각하지 않았고, 단순히 끌리는 것들을 정한 뒤 빠르게 기본적인 틀을 작성하기 시작했다.

Class Diagram

다행히 레퍼런스 게임의 시스템이 간단하고 직관적이었기에 구조를 구상하는 것에는 전혀 문제가 없었다. 미니 게임에서 베팅을 하여 돈을 따고, 빚을 갚아나가는 것을 기본 골자로 시간 개념을 적용하기에는 무리가 있을 것 같아, 시간의 흐름에 따른 체력 감소와 이자 증가를 제외하고 기본 뼈대에 살을 채웠다.

기본적인 구조는 다음과 같이 구성했다. GameManager가 게임의 전체적인 흐름을 관리하고 각 Place에는 상호작용할 수 있는, 독립적인 GameObject가 존재하여 Player를 통해 게임을 진행하는 것이다. 이때 Player와 GameManager는 싱글톤으로 구현하여 인스턴스 충돌을 방지했으며, 조금 더 직관적으로 코드를 작성할 수 있도록 하였다.

Singleton

앞서 언급했으니 간단하게 다시 말하자면, 인스턴스가 하나만 존재해야하는 클래스에 활용되는 디자인 패턴이다. 이번 프로젝트에서는 GameManager와 Player 및 각각의 Objcet에 활용하여 조금 더 직관적인 코드를 구현하고자 하였다.

이를 바탕으로 조금 더 세부적으로 들어가면 총 5개의 필드(Casino, Lottery, Home, Smithy, Field)에 상호작용할 수 있는 오브젝트가 존재하며, 해당 오브젝트는 각각 싱글톤 클래스에 의존하여 구현되고 있는 것이다.

다만 단순히 필드 이동 기능 만을 가지고 있는 Object나 단순 스크립트만 존재하는 경우 따로 클래스를 추가하여 제작하지는 않고, Object 내부에서 처리하도록 구현하였다.

Util

기본적인 틀을 잡고 나서 가장 먼저한 일은 시스템, 미니 게임 기능 구현을 위한 간단한 유틸적인 기능을 Util static 클래스에 따로 구현하여 전역적으로 사용가능하게끔 한 것이다. 다양한 기능들을 구현했으며, 이는 2일차 Til에 간단히 정리했으니 이를 참고하면 될 것 같다.

Class

이번 프로젝트의 이름인 OOP는 Object-Oriented Programming 즉 객체지향 프로그래밍을 의미한다. 그렇긴에 객체지향의 꽃 Class를 제대로 활용할 수 있어야했고, 배운 것을 바탕으로 소스코드를 작성해보았다.

근데 프로젝트가 끝나고 나서 돌이켜보니 어떻게 하면 객체지향을 지키며 장점을 부각시킬 수 있을지에 대해 고민하면서 프로젝트에 임한 것 같지는 않다. IDE를 켜고 본격적으로 시작하기 전까지는 기대감에 차서 머릿속에 구상을 잔뜩했는데 막상 시작하고나서는 뼈대를 잡는 것까지는 어찌어찌 가능했으나, 갑작스럽게 느껴지는 마감 기한의 압박과 함께 살을 붙이는 과정에서 난잡하게 클래스를 생성하고 의존 관계나 상속 등을 활용하지 않고 기능 구현에 초점을 맞춰버린 것이다.

최대한 클래스를 분리하고 기능을 쪼개서 단일 책임 원칙만 지켜보고자 했지만 이 역시 쉽지않았다. 일단 기능 구현을 해내고 제출까지 했지만 비교적 마음에 들지 않는 상태에서 프로젝트가 끝나고 다른 분들과 교류하는 시간에 몇몇 분들의 코드를 참고할 수 있었는데, 의존성 주입을 통한 코드 개선의 방식이나 인터페이스 분리 원칙에 따른 다양한 인터페이스 구현 등이 굉장히 인상적이었다. 다시금 공부를 더 열심히 해야겠다는 생각과 함께 클래스에 대한 이야기는 마치겠다.

Bug

객체지향의 꽃이 Class라면 프로그래밍의 꽃은 Bug가 아닐까. 이번 프로젝트가 정말 마감기한이 엄격하고 나에 대한 평가가 집중되는 중요한 프로젝트였다면 버그가 생길때마다 스트레스가 엄청났겠지만, 이제 한 달차인 뉴비에게 있어 버그는 나의 부족한 점을 인지할 수 있게 해주는 고마운 메타인지 수단이다.

수많은 개념들을 머릿속에 때려넣으면서 내가 해당 알고리즘을 이해하고 있는지, 자료구조가 머릿속에 잡혀있는지는 나조차 아직 모르기에, 프로젝트를 진행하면서 생기는 버그들은 내가 가지고 있는 개념 오류를 의미할 것이다. 따라서 버그를 해결하면서 조금씩 머릿속 구멍을 메우는 것이라고 생각했고 이로 인해 스트레스는 크게 다가온 것 같지는 않았다.

프로젝트를 진행하면서 버그는 구문 오류까지 셈해서 거의 50번 넘게 발생한 것 같은데 모든 버그를 커밋할 수는 없었고, 20개의 버그들을 수정하는 과정만을 깃에 저장할 수 있었다. 사실 오늘 버그 정리하려했는데 잠에 들어버려...

Show off

버그 정리를 하고자 했으나 내일의 나에게 미루기로 하고 간단하게 자랑할 점에 대해 말해볼까한다. 사실 프로젝트 제출 이후 강사님께서 따로 발표하고 싶은 사람이 있는지 물어보는 질문에 손을 들어볼까 했지만, 이후 발표하신 분들의 퀄리티를 보니 안 들기를 잘했다는 생각이 들었다. 그렇다고 아예 나의 프로젝트가 못 보여줄 수준이라고는 생각하지는 않기에 이것저것 말해보겠다.

Slot Machine

슬롯머신의 경우 각 슬롯마다 돌아가는 속도가 차이나도록 구현하고자 했는데 Thread.SleepConsole.keyAvailable 및 배열 요소의 중복을 활용하여 비교적 쓸만하게 만든 것 같다는 생각을 했다.

private void PrintSlot
(int left, int right, out int count1, out int count2, out int count3)
{
    ConsoleKey input = Console.ReadKey().Key;
    int count = -1;
    while (!Console.KeyAvailable)
    {
        for (int i = 0; i < _right.Length; i++)
        {
            if (Console.KeyAvailable) break;

            Console.SetCursorPosition(left, right);
            Console.Write(_left[i]);
            Console.SetCursorPosition(left + 5, right);
            Console.Write(_mid[i]);
            Console.SetCursorPosition(left + 10, right);
            Console.Write(_right[i]);
            Thread.Sleep(_level);
            count += 1;
        }
    }
    Console.SetCursorPosition(left, right);
    Console.Write(_left[count % 24]);
    count1 = count;
    input = Console.ReadKey().Key;

    while (!Console.KeyAvailable)
    {
        for (int i = 0; i < _right.Length; i++)
        {
            if (Console.KeyAvailable) break;

            Console.SetCursorPosition(left + 5, right);
            Console.Write(_mid[i]);
            Console.SetCursorPosition(left + 10, right);
            Console.Write(_right[i]);
            Thread.Sleep(_level);
            count += 1;
        }
    }
    Console.SetCursorPosition(left + 5, right);
    Console.Write(_mid[count % 24]);
    count2 = count;
    input = Console.ReadKey().Key;

    while (!Console.KeyAvailable)
    {
        for (int i = 0; i < _right.Length; i++)
        {
            if (Console.KeyAvailable) break;

            Console.SetCursorPosition(left + 10, right);
            Console.Write(_right[i]);
            Thread.Sleep(_level);
            count += 1;
        }
    }
    Console.SetCursorPosition(left + 10, right);
    Console.Write(_right[count % 24]);
    count3 = count;
}

_left, _mid, _right는 슬롯머신이 가지고 있는 슬롯의 문자 출력 순서라고 보면 되는데 중복된 요소를 연달아 배치하여 마치 느리게 움직이는 듯한 효과를 주었다. KeyAvailable을 통해 입력이 없으면 계속해서 돌아가고 입력을 하면 왼쪽부터 슬롯이 멈추는 효과를 구현할 수 있었고, Thread.Sleep(_level)에서 난이도에 따라 돌아가는 속도를 조절할 수 있도록 했다.

Input number Method

이건 미니게임에서 베팅 금액 입력에 활용된 유틸 기능으로 좌우 방향키를 통해 자릿값을 이동하고 위아래 방향키를 통해 해당 자릿값의 숫자를 바꿔 최종적으로 엔터키를 통해 숫자를 입력하도록 구현하였다. 사실 어떤 미니 게임 구현보다 해당 유틸 기능 구현이 어려웠기에 가장 기억에 남는 것 같다.

public static void InputNumber (int x, int y, params int[] number)
{
    int left;
    int right;
    int decision = x + 1;
    int b = y + 1;
    ConsoleKey newInput = ConsoleKey.Add;

    for (int i = 0; i < number.Length; i++)
    {
        Console.SetCursorPosition(x + i, y);
        Console.WriteLine(number[i]);
    }

    Console.SetCursorPosition(x, y + 1);
    Console.Write("▲");
    newInput = Console.ReadKey().Key;
    (left, right) = Console.GetCursorPosition();
    while (newInput != ConsoleKey.Enter)
    {
        if (left - 1 <= x && (left - 1 + number.Length - 1) > x
                          && newInput == ConsoleKey.RightArrow)
        {
            Console.Write("\b");
            Console.Write(" ");
            Console.SetCursorPosition(x += 1, y + 1);
            Console.Write("▲");
        }
        else if (left - 1 < x && (left - 1 + number.Length - 1) >= x
                              && newInput == ConsoleKey.LeftArrow)
        {
            Console.Write("\b");
            Console.Write(" ");
            Console.SetCursorPosition(x -= 1, y + 1);
            Console.Write("▲");
        }
        else if (newInput == ConsoleKey.UpArrow)
        {
            Console.SetCursorPosition(decision, b - 1);
            Console.Write("\b");
            Console.Write(" ");
            Console.Write("\b");
            if (number[decision - left] + 1 < 10)
            {
                Console.Write(number[decision - left] + 1);
                number[decision - left] += 1;
            }
            else if (number[decision - left] + 1 == 10)
            {
                Console.Write(0);
                number[decision - left] = 0;
            }
            Console.SetCursorPosition(decision, b);
        }
        else if (newInput == ConsoleKey.DownArrow)
        {
            Console.SetCursorPosition(decision, b - 1);
            Console.Write("\b");
            Console.Write(" ");
            Console.Write("\b");
            if (number[decision - left] - 1 >= 0)
            {
                Console.Write(number[decision - left] - 1);
                number[decision - left] -= 1;
            }
            else if (number[decision - left] - 1 < 0)
            {
                Console.Write(9);
                number[decision - left] = 9;
            }
            Console.SetCursorPosition(decision, b);
        }
        newInput = Console.ReadKey().Key;
    }
}

매개변수 x,y는 입력 부분이 노출되는 좌표를 의미하며 params는 입력하고자하는 숫자의 자릿수의 크기를 가진 배열이다. 예를 들어 new int[7]로 초기화를 했다면 7자리가 출력되는 것이다. 1일차 새벽에 구현을 시도해서 그런지 비교적 오래 걸린 기능으로 그냥 자랑하고 싶었다.

impression

마지막으로 소회? 소감?을 말해보자면 정말 재밌었다라고 말하고 싶다. 물론 끝나고 나서 돌이켜보니... 라는 느낌이지만 레퍼런스 게임을 참고하면서 이건 어떻게 구현할 수 있을까? 를 머릿속에서 구상하는 과정이 너무 즐거웠던 것 같다. 프로젝트를 진행하면서 중간에 밥을 먹을 때나 잠에 들기 직전까지 머릿속에서 계속 구현하고 실마리가 잡히면 다시 컴퓨터 앞에 앉아서 테스트 코드를 작성하는 것도 너무 재밌었고, 이러한 내 모습을 보는 것도 꽤나 재밌는 일이었다. 나의 부족한 부분을 알 수 있는 좋은 기회기도 했지만, 무엇보다 내가 정말 재밌게 할 수 있는 활동이구나하고 다시 한 번 느낄 수 있었다.

제출이 끝나고 다른 분들과 교류하는 시간을 가지며 다양한 코드 방식을 배울 수 있었는데 앞으로 배울게 많다는 것을 느꼈고, 조금 더 분발하자는 마음이 들었다. 사실 제출 직전에는 피곤해 죽을 것 같았는데, 조금 자고 와서 사람이 긍정적으로 변한 것도 조금 감안해야할 것 같다. 그래도 3일 동안 나를 포함해서 함께 프로젝트를 진행한 모든 분들께 고생많았다고 말해주고 싶다. 정말 즐거운 3일이었다!

profile
뚠뚠뚠뚠

0개의 댓글