프로젝트/C# 머드게임

C# 머드 게임 프로젝트 (2) - 구조 변경, 기능 추가

오의현 2024. 7. 28. 16:02

지난 번에는 대충 어떤 식으로 콘솔을 입력하고 머드 게임을 구성하는지 테스트 해보던 단계라 기능이 분리되어 있지 않았다. 그래서 이번엔 클래스를 조금 더 잘게 분리한 뒤 몇 가지 기능을 추가하였다.

 

먼저 GameCore의 코드이다.

더보기
더보기
using C_Study;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace C_Study
{
    public enum LevelType
    {
        Start,
        Setting,
        Menu,
        Play,
    }

    public class GameCore
    {

        public void Start()
        {
            _player = new Player();
            _levelType = LevelType.Start;

            _levels = new Dictionary<LevelType, GameLevel>();

            _levels.Add(LevelType.Start, new StartLevel(this));
            _levels.Add(LevelType.Setting, new SettingLevel(this));
            _levels.Add(LevelType.Menu, new MenuLevel(this));
        }

        public void Update()
        {
            while (_player != null)
            {
                _levels[_levelType].Update();
            }
        }

        public void End()
        {

        }

        public LevelType CurrentLevel
        {
            set { _levelType = value; }
        }

        public Player CurrentPlayer
        {
            get { return _player; }
        }


        private Player _player;
        private LevelType _levelType;

        private Dictionary<LevelType, GameLevel> _levels;
    }
}

지난번엔 GameCore 내부에 모든 기능을 구현해서 업데이트 함수를 돌리고 있었지만, Level관련 인스턴스를 따로 만들어서 Dictionary 자료구조에 삽입하였고 GameCore는 update함수만 호출하도록 하였다.

 

각 레벨은 생성자에서 GameCore를 인자로 받으면, 해당 Core를 자료구조에 보유하도록 하였다.

Level에서 Core의 옵션을 변경하면서 레벨을 왔다갔다 하기 위해서이다.

 

모든 Level클래스는 GameLevel이라는 추상 클래스를 상속받도록 구현하였다.

 

아래는 GameLevel 클래스이다.

더보기
더보기
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace C_Study
{
    public abstract class GameLevel
    {
        protected GameLevel() { }

        public abstract void Update();

        protected GameCore _parentCore;

    }
}

아래는 이를 상속받은 클래스들이다.

 

시작 화면을 담당하는 StartLevel.

더보기
더보기
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace C_Study
{
    public class StartLevel : GameLevel
    {
        public StartLevel(GameCore parrentCore)
        {
            _parentCore = parrentCore;
        }

        public override void Update()
        {
            Console.Clear();

            Console.WriteLine("*************************************************");
            Console.WriteLine("*************************************************");

            Console.Write("*****************");
            DevFunctions.WriteColored("전사의 모험 RPG", ConsoleColor.Yellow);
            Console.WriteLine("*****************");

            Console.Write("******************");
            DevFunctions.WriteColored("제작자 오의현", ConsoleColor.Yellow);
            Console.WriteLine("******************");

            Console.WriteLine("*************************************************");
            Console.WriteLine("*************************************************");
            Console.WriteLine();

            Console.WriteLine("모험을 시작하시겠습니까?");

            DevFunctions.WriteLineColored("1 : YES", ConsoleColor.Blue);
            DevFunctions.WriteLineColored("2 : NO", ConsoleColor.Red);

            string input = Console.ReadLine();

            if (DevFunctions.IsNumeric(input) == false)
            {
                return;
            }

            int toInt = int.Parse(input);

            switch (toInt)
            {
                case 1:
                    _parentCore.CurrentLevel = LevelType.Setting;
                    break;
                case 2:
                    Environment.Exit(0);
                    break;
            }
        }

    }
}

 

캐릭터 선택을 담당하는 SettingLevel

(아직은 무기 선택만 있지만, 나중에는 뭐 여러가지 추가할 수도 있다. 안할수도 있고..)

더보기
더보기
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace C_Study
{
    public class SettingLevel : GameLevel
    {

        public SettingLevel(GameCore parrentCore)
        {
            _parentCore = parrentCore;
        }

        public override void Update()
        {
            WeaponSelect();
            WeaponFixOrReselect();
        }

        private void WeaponSelect()
        {
            while (true)
            {
                Console.Clear();

                Console.WriteLine("-------------------------------------------------");
                Console.WriteLine("-------------------------------------------------");
                Console.WriteLine("----------------캐릭터 설정 단계-----------------");
                Console.WriteLine("-------------------------------------------------");
                Console.WriteLine("-------------------------------------------------");
                Console.WriteLine();

                DevFunctions.WriteLineColored("무기 선택 단계입니다.", ConsoleColor.Cyan);
                Console.WriteLine();

                Console.WriteLine("검은 기본 공격력이 낮지만 크리티컬 확률이 가장 높습니다.");
                Console.WriteLine("공격시 일정 확률로 출혈을 발생시키기도 합니다.");
                Console.WriteLine();

                Console.WriteLine("창은 적당한 공격력과 크리티컬 확률을 보유하고 있습니다.");
                Console.WriteLine("피격시 일정 확률로 적의 공격을 회피합니다.");
                Console.WriteLine();

                Console.WriteLine("망치는 높은 공격력을 보유하고 있지만 크리티컬 확률이 낮습니다.");
                Console.WriteLine("공격시 일정 확률로 적을 기절 상태로 만듭니다.");
                Console.WriteLine();

                Console.WriteLine("어떤 무기를 선택하시겠습니까?");
                DevFunctions.WriteColored("1.오래된 검 ", ConsoleColor.Blue);
                DevFunctions.WriteColored("2.오래된 창 ", ConsoleColor.Red);
                DevFunctions.WriteColored("3.오래된 망치", ConsoleColor.Green);
                Console.WriteLine();

                string input = Console.ReadLine();

                if (DevFunctions.IsNumeric(input) == false)
                {
                    continue;
                }

                if (input.Length > 1)
                {
                    continue;
                }

                int toInt = int.Parse(input);

                switch (toInt)
                {
                    case 1:
                        _parentCore.CurrentPlayer.EquipedWeapon = new OldSword();
                        break;
                    case 2:
                        _parentCore.CurrentPlayer.EquipedWeapon = new OldSpear();
                        break;
                    case 3:
                        _parentCore.CurrentPlayer.EquipedWeapon = new OldHammer();
                        break;
                }

                break;
            }
        }

        private void WeaponFixOrReselect()
        {
            while (true)
            {
                Console.Clear();

                Console.WriteLine("-------------------------------------------------");
                Console.WriteLine("-------------------------------------------------");
                Console.WriteLine("----------------캐릭터 설정 단계-----------------");
                Console.WriteLine("-------------------------------------------------");
                Console.WriteLine("-------------------------------------------------");
                Console.WriteLine();

                Console.Write("선택하신 무기는 ");
                DevFunctions.WriteColored(_parentCore.CurrentPlayer.EquipedWeapon.WPName, ConsoleColor.Cyan);
                Console.Write("입니다.");
                Console.WriteLine();

                Console.WriteLine("선택하신 무기로 게임을 시작하시겠습니까?");
                DevFunctions.WriteLineColored("1. 네", ConsoleColor.Blue);
                DevFunctions.WriteLineColored("2. 다시 선택할래요", ConsoleColor.Red);

                string input = Console.ReadLine();

                if (DevFunctions.IsNumeric(input) == false)
                {
                    continue;
                }

                if (input.Length > 1)
                {
                    continue;
                }

                int toInt = int.Parse(input);

                if(toInt != 1 && toInt != 2)
                {
                    continue;
                }

                switch (toInt)
                {
                    case 1:
                        _parentCore.CurrentLevel = LevelType.Menu;
                        break;
                    case 2:
                        _parentCore.CurrentLevel = LevelType.Setting;
                        break;
                }

                break;
            }
        }
    }
}

아래는 인벤토리 보기, 캐릭터 스탯 보기, 전투하기 등의 메뉴를 선택하는 MenuLevel이다.

_parentCore에서 멤버변수를 사용하는 코드가 너무 길어서, 별도의 get를 따로 만들어야 할 듯 하다.

더보기
더보기
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace C_Study
{
    public class MenuLevel : GameLevel
    {
        private delegate void UpdateFunc();
        private delegate void SelectFunc();

        public MenuLevel(GameCore parrentCore)
        {
            _parentCore = parrentCore;

            selectfunc = new SelectFunc(SelectMenu);
        }

        public override void Update()
        {
            Console.Clear();

            if (updatefunc != null)
            {
                updatefunc();
            }

            if (selectfunc != null)
            {
                selectfunc();
            }
        }

        private void WriteStatus()
        {
            DevFunctions.WriteLineColored("착용중인 무기 정보", ConsoleColor.DarkCyan);

            Console.WriteLine("이름 : {0}  ", _parentCore.CurrentPlayer.EquipedWeapon.WPName);
            Console.WriteLine("공격력 : {0}  ", _parentCore.CurrentPlayer.EquipedWeapon.AttPower);
            Console.WriteLine("크리티컬 확률 : {0}%  ", _parentCore.CurrentPlayer.EquipedWeapon.CriticalProb);
            Console.WriteLine();

            DevFunctions.WriteLineColored("플레이어 스탯 정보", ConsoleColor.DarkCyan);
            Console.WriteLine("레벨 : {0}  ", _parentCore.CurrentPlayer.Level);
            Console.WriteLine("잔여 스탯 포인트 : {0}  ", _parentCore.CurrentPlayer.StatPoint);
            Console.WriteLine("공격력 : {0} ({1} + {2})  ", _parentCore.CurrentPlayer.AttPower + _parentCore.CurrentPlayer.EquipedWeapon.AttPower, _parentCore.CurrentPlayer.AttPower, _parentCore.CurrentPlayer.EquipedWeapon.AttPower);
            Console.WriteLine("크리티컬 확률 : {0} ({1} + {2})%  ", _parentCore.CurrentPlayer.CriticalProb + _parentCore.CurrentPlayer.EquipedWeapon.CriticalProb, _parentCore.CurrentPlayer.CriticalProb, _parentCore.CurrentPlayer.EquipedWeapon.CriticalProb);
            Console.WriteLine("크리티컬 데미지 : {0}%  ", _parentCore.CurrentPlayer.CriticalPower);
            Console.Write("무기 숙련도 : {0}  ", _parentCore.CurrentPlayer.WeaponSkilled);
            DevFunctions.WriteLineColored("*무기 숙련도가 높을수록 출혈, 회피, 기절 확률이 증가합니다.", ConsoleColor.DarkRed);
            Console.WriteLine("방어력 : {0}  ", _parentCore.CurrentPlayer.DefPower);
            Console.WriteLine("체력 : {0} / {1}  ", _parentCore.CurrentPlayer.CurrentHP, _parentCore.CurrentPlayer.MaxHP);
            Console.WriteLine("경험치 : {0} / {1}  ", _parentCore.CurrentPlayer.CurrentEXP, _parentCore.CurrentPlayer.MaxEXP);
            Console.WriteLine();
        }

        private void WriteInventory()
        {
            DevFunctions.WriteLineColored("보유 아이템 정보", ConsoleColor.DarkCyan);
            Console.WriteLine("아이템 개수 : {0}  ", _parentCore.CurrentPlayer.Inventory.Count);
            Console.WriteLine();
        }

        private void SelectMenu()
        {
            while (true)
            {
                Console.WriteLine("1. 캐릭터 정보를 확인한다.");
                Console.WriteLine("2. 인벤토리를 확인한다.");
                Console.WriteLine("3. 전투를 시작한다.");
                Console.WriteLine("4. 게임을 종료한다.");

                string input = Console.ReadLine();

                if (DevFunctions.IsNumeric(input) == false)
                {
                    continue;
                }

                if (input.Length > 1)
                {
                    continue;
                }

                int toInt = int.Parse(input);

                switch (toInt)
                {
                    case 1:
                        updatefunc = new UpdateFunc(WriteStatus);
                        break;
                    case 2:
                        updatefunc = new UpdateFunc(WriteInventory);
                        break;
                    case 3:
                        updatefunc = null;
                        selectfunc = new SelectFunc(SelectMonster);
                        break;
                }

                break;
            }
        }

        private void SelectMonster()
        {
            while (true)
            {
                DevFunctions.WriteLineColored("전투하고 싶은 몬스터를 선택하세요.", ConsoleColor.DarkCyan);
                Console.WriteLine();

                Console.WriteLine("1. 고블린 (적정 레벨 : 1)");
                Console.WriteLine("2. 오우거 (적정 레벨 : 5)");
                Console.WriteLine("3. 드래곤 (적정 레벨 : 10)");
                Console.WriteLine("4. 이전으로 돌아간다.");

                string input = Console.ReadLine();

                if (DevFunctions.IsNumeric(input) == false)
                {
                    continue;
                }

                if (input.Length > 1)
                {
                    continue;
                }

                int toInt = int.Parse(input);

                switch (toInt)
                {
                    case 4:
                        selectfunc = new SelectFunc(SelectMenu);
                        break;
                }
                break;
            }
        }

        private UpdateFunc updatefunc;
        private SelectFunc selectfunc;
    }
}

 

레벨은 일단 여기까지 구현하였고, Player 클래스 내부에도 몇가지 속성을 추가하였다.

아래는 Player 클래스이다.

더보기
더보기
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Threading.Tasks;

namespace C_Study
{
    public class Player
    {
        public Player()
        {
            _attPower = 10;
            _defPower = 10;

            _maxHP = 100;
            _currentHP = 100;

            _maxEXP = 100;
            _currentEXP = 0;

            _criticalPower = 150.0f;
            _criticalProb = 5.0f;

            _level = 1;
            _statPoint = 0;

            _weaponSkilled = 1;

            _inventory = new List<Item>();
        }

        public Weapon EquipedWeapon
        {
            get { return _equipedWeapon; }
            set { _equipedWeapon = value; }
        }

        public int AttPower
        {
            get { return _attPower; }
        }
        public int DefPower
        {
            get { return _defPower; }
        }

        public int Level
        {
            get { return _level; }
        }

        public int StatPoint
        {
            get { return _statPoint; }
        }

        public int WeaponSkilled
        {
            get { return _weaponSkilled; }
        }

        public int MaxHP
        {
            get { return _maxHP; }
        }

        public int CurrentHP
        {
            get { return _currentHP; }
        }

        public int MaxEXP
        {
            get { return _maxEXP; }
        }

        public int CurrentEXP
        {
            get { return _currentEXP; }
        }


        public float CriticalPower
        {
            get { return _criticalPower; }
        }

        public float CriticalProb
        {
            get { return _criticalProb; }
        }
        public List<Item> Inventory
        {
            get { return _inventory; }
        }

        private Weapon _equipedWeapon;

        private int _attPower;
        private int _defPower;

        private float _criticalPower;
        private float _criticalProb;

        private int _level;
        private int _statPoint;
        private int _weaponSkilled;

        
        private int _maxHP;
        private int _currentHP;

        private int _maxEXP;
        private int _currentEXP;

        List<Item> _inventory;
    }

}

코드를 보면 멤버변수에 인벤토리가 있는데, item 클래스를 만들어서 이를 상속받은 아이템들을 저장할 것이다.

Item클래스는 아래와 같다.

더보기
더보기
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace C_Study
{
    public class Item
    {
        protected Item()
        {
        }

        public int AttPower
        {
            get { return _attPower; }
            protected set { _attPower = value; }
        }
        public int DefPower
        {
            get { return _defPower; }
            protected set { _defPower = value; }
        }

        public int WeaponSkilled
        {
            get { return _weaponSkilled; }
            protected set { _weaponSkilled = value; }
        }

        public int HealHP
        {
            get { return _healHP; }
            protected set { _healHP = value; }
        }

        public float CriticalPower
        {
            get { return _criticalPower; }
            protected set { _criticalPower = value; }
        }

        public float CriticalProb
        {
            get { return _criticalProb; }
            protected set { _criticalProb = value; }
        }

        public int RemainTurn
        {
            get { return _remainTurn; }
            protected set { _remainTurn = value; }
        }


        private int _attPower;
        private int _defPower;

        private float _criticalPower;
        private float _criticalProb;

        private int _weaponSkilled;

        private int _healHP;
        private int _remainTurn;
    }
    
    public class Apple : Item
    {
        public Apple()
        {
            HealHP = 50;
        }
    }
}

 

 소비 아이템만 만들 것이기 때문에, 이 아이템으로 인해 상승될 수 있는 옵션들을 저장하였다.

 

무기 클래스도 조금 개선하였다.

검 종류에는 출혈 확률을 추가하였고, 창 종류에는 회피 확률, 망치 종류에는 기절 확률을 추가하였다.

더보기
더보기
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;



namespace C_Study
{
    public enum WeaponType
    {
        None,
        Sword,
        Spear,
        Hammer,
    }

    public class Weapon
    {
        protected Weapon()
        {

        }
        public WeaponType WPType
        {
            get { return _wpType;  }
            protected set { _wpType = value; }
        }

        public string WPName
        {
            get { return _wpName; }
            protected set { _wpName = value; }
        }
        public int AttPower
        {
            get { return _attPower; }
            protected set { _attPower = value; }
        }
        public float CriticalProb
        {
            get { return _criticalProb; }
            protected set { _criticalProb = value; }
        }

        private int _attPower;
        private float _criticalProb;

        private string _wpName;
        private WeaponType _wpType;
    }

    public class Sword : Weapon
    {
        protected Sword()
        {
            WPType = WeaponType.Sword;
            CriticalProb = 25.0f;
        }

        protected float BleedingProb
        { 
            get { return _bleedingProb; }
            set { _bleedingProb = value; }  
        }

        private float _bleedingProb;
    }

    public class Spear : Weapon
    {
        protected Spear()
        {
            WPType = WeaponType.Spear;
            CriticalProb = 15.0f;
        }

        protected float EvadingProb
        {
            get { return _evadingProb; }
            set { _evadingProb = value; }
        }

        private float _evadingProb;
    }

    public class Hammer : Weapon
    {
        protected Hammer()
        {
            WPType = WeaponType.Hammer;
            CriticalProb = 5.0f;
        }

        protected float StunProb
        {
            get { return _stunProb; }
            set { _stunProb = value; }
        }

        private float _stunProb;
    }

    public class OldSword : Sword
    {
        public OldSword()
        {
            WPName = "오래된 검";
            AttPower = 10;
            BleedingProb = 5.0f;
        }
    }

    public class OldSpear : Spear
    {
        public OldSpear()
        {
            WPName = "오래된 창";
            AttPower = 15;
            EvadingProb = 10.0f;
        }
    }

    public class OldHammer : Hammer
    {
        public OldHammer()
        {
            WPName = "오래된 망치";
            AttPower = 30;
            StunProb = 5.0f;
        }
    }
}

아래는 지금까지 구현된 것을 영상으로 찍은 것이다.

 

C++의 문법에 점점 익숙해지는중..