이번엔 전투의 일부를 구현하였다.

 

아래는 전투가 이루어지는 레벨의 코드 전체이다.

더보기
using System;
using System.Collections.Generic;
using System.Diagnostics.SymbolStore;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace C_Study
{
    public class FightLevel : GameLevel
    {
        public override void Update()
        {
            Console.Clear();
            
            if(SelectedMonster == null)
            {
                SelectMonster();
            }
            else
            {
                Fight();
            }
        }

        public FightLevel(GameCore parrentCore)
        {
            _parentCore = parrentCore;
            _playerLog = null;
            _monsterLog = null;
        }

        public void Fight()
        {
            if(_selectedMonster == null)
            {
                return;
            }

            DevFunctions.WriteLineColored("플레이어 정보", ConsoleColor.Blue);
            Console.WriteLine("HP : {0} / {1}", _parentCore.CurrentPlayer.CurrentHP, _parentCore.CurrentPlayer.MaxHP);
            Console.WriteLine();

            DevFunctions.WriteLineColored("몬스터 정보", ConsoleColor.Red);
            Console.WriteLine("이름 : {0}", _selectedMonster.Name);
            Console.WriteLine("HP : {0} / {1}", _selectedMonster.CurrentHP, _selectedMonster.MaxHP);
            Console.WriteLine();

            if (_selectedMonster.CurrentHP <= 0)
            {
                Win();   
            }
            else if(_parentCore.CurrentPlayer.CurrentHP <= 0)
            {
                Lose();   
            }
            else
            {
                WriteFightLog();
                SelectBehavior();
                MonsterBehavior();
            }
        }

        private void Win()
        {
            Console.WriteLine("전투에서 승리하였습니다!");

            _parentCore.CurrentPlayer.CurrentEXP += _selectedMonster.WinEXP;
            _parentCore.CurrentPlayer.Money += _selectedMonster.WinMoney;

            Console.WriteLine("아무 키나 입력시 메뉴 선택창으로 돌아갑니다.");

            Console.Read();

            _selectedMonster = null;
            _monsterLog = null;
            _playerLog = null;

            _parentCore.CurrentLevel = LevelType.Menu;
        }
        private void Lose()
        {
            Console.WriteLine("전투에서 패배하였습니다..");
            Console.WriteLine("아무 키나 입력시 메뉴 선택창으로 돌아갑니다.");

            Console.Read();

            _selectedMonster = null;
            _monsterLog = null;
            _playerLog = null;

            _parentCore.CurrentPlayer.CurrentHP = 1;
            _parentCore.CurrentLevel = LevelType.Menu;
        }


        private void WriteFightLog()
        {
            DevFunctions.WriteLineColored("나의 행동", ConsoleColor.Blue);
            
            if(_playerLog != null)
            {
                Console.WriteLine("{0}", _playerLog);
                Console.WriteLine();
            }

            DevFunctions.WriteLineColored("적의 행동", ConsoleColor.Red);
            if(_monsterLog != null)
            {
                Console.WriteLine("{0}", _monsterLog);
            }

            Console.WriteLine();
        }

        private void SelectBehavior()
        {
            Console.WriteLine("1. 공격한다.\n2. 아이템을 사용한다.\n3. 도망친다.");

            string input = Console.ReadLine();

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

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

            int toInt = int.Parse(input);

            switch (toInt)
            {
                case 1:
                    PlayerAttack();
                    break;
            }
        }

        private void PlayerAttack()
        {
            int Damage = GameFunctions.GetCalculatedDamage(_parentCore.CurrentPlayer, _selectedMonster);
            bool isCritical = GameFunctions.isCritical(_parentCore.CurrentPlayer);

            string CriticalMsg = "";

            if(isCritical == true) 
            {
                Damage = (int)(Damage * _parentCore.CurrentPlayer.CriticalPower);
                CriticalMsg = "[크리티컬!] ";
            }

            _playerLog = "적에게 공격 : " + CriticalMsg + Damage.ToString() + "의 피해를 입혔습니다.";

            _selectedMonster.CurrentHP -= Damage;
        }
        private void MonsterBehavior()
        {
            if(_selectedMonster.CurrentHP <= 0)
            {
                _monsterLog = "사망하였습니다.";
                return;
            }

            int Damage = GameFunctions.GetCalculatedDamage(_selectedMonster, _parentCore.CurrentPlayer);
            bool isCritical = GameFunctions.isCritical(_selectedMonster);

            string CriticalMsg = "";

            if (isCritical == true)
            {
                Damage = (int)(Damage * _selectedMonster.CriticalPower);
                CriticalMsg = "[크리티컬!] ";
            }

            _monsterLog = "플레이어에게 공격 : " + CriticalMsg + Damage.ToString() + "의 피해를 입혔습니다.";

            _parentCore.CurrentPlayer.CurrentHP -= Damage;
        }

        private void SelectMonster()
        {
            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)
            {
                return;
            }

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

            int toInt = int.Parse(input);

            switch (toInt)
            {
                case 1:
                    _selectedMonster = new Goblin();
                    break;
                case 4:
                    _parentCore.CurrentLevel = LevelType.Menu;
                    _selectedMonster = null;
                    break;
            }
        }

        public static Monster SelectedMonster
        {
            get { return _selectedMonster; }
            set { _selectedMonster = value; }
        }

        private static Monster _selectedMonster;
        private static string _playerLog;
        private static string _monsterLog;
    }
}

현재는 공격에 대한 기능만 구현이 되어있고, 아이템 사용은 아직 구현하지 않았다.

공격을 실시하면 몬스터의 HP가 깎이고 몬스터도 플레이어에게 즉시 공격을 실시한다.

몬스터의 HP가 0이 되면, Win함수가 호출되고  적의 HP가 0이되면 Lose함수가 호출된다.

 

Win함수가 호출되면, 플레이어는 경험치와 돈을 획득한다.

 

아래는 플레이어 경험치 변수의 set이 호출될 때 작업을 작성한 것이다.

여기서 레벨 업이 진행되도록 하였다.

더보기
public int CurrentEXP
{
    get { return _currentEXP; }
    set 
    {
        _currentEXP = value;

        if (_currentEXP >= _maxEXP)
        {
            LevelUp();
        }
    }
}

private void LevelUp()
{
    _currentEXP -= _maxEXP;
    Console.WriteLine("레벨이 상승하였습니다!");

    MaxEXP = (int)(_maxEXP * 1.1f);

    _level++;

    _attPower += 10;
    _defPower += 2;
}

레벨이 오르면, 경험치 수치가 조정되고 공격력과 방어력이 일정 수치만큼 오르도록 하였다.

최대 경험치 수치도 동시에 증가한다.

 

아래 코드는 데미지를 계산하는 것과 크리티컬 여부를 판단하는 함수이다.

더보기
public class GameFunctions
{
    public static int GetCalculatedDamage(ICreatureStat attacking, ICreatureStat attacked)
    {
        Random random = new Random();

        int MinAtt = (int)(attacking.AttPower * 0.9f);
        int MaxAtt = (int)(attacking.AttPower * 1.1f);

        int Att = random.Next(MinAtt, MaxAtt);
        int Def = attacked.DefPower;

        float DefRate = Def / 100.0f;
        DefRate = 1.0f - DefRate;

        return (int)(Att * DefRate);
    }

    public static bool isCritical(ICreatureStat attacking)
    {
        Random random = new Random();
        int randomValue = random.Next(0, 100);

        if (randomValue <= attacking.CriticalProb_100)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

 

이 함수를 활용하여 최종 데미지를 산출하였다.

 

아래 동영상은 구현된 결과이다.

 

이렇게 공격을 주고받는 기능은 구현하였다.

다음 번엔 아이템을 사용하는 것과 무기의 특수효과를 적용할 예정이다.

 

그리고 추가적으로 인벤토리 아이템을 출력할 때, 아이템을 이름 순으로 정렬하도록 했는데 정렬 방법이 C++과 조금 달랐다. C++은 비교 연산자를 오버로딩하여 사용하지만 C#은 IComparable 인터페이스를 상속받은 뒤 CompareTo라는 함수를 구현해야 정렬이 가능했다. 

 int IComparable<Item>.CompareTo(Item other)
 {
     if(other.Name.CompareTo(Name) > 0)
     {
         return -1;
     }
     else
     {
         return 1;
     }
 }

 그래서 Item 클래스에 요런 함수를 추가로 작성해주었다.

일단 전투를 추가하기 전에 상점 기능을 추가하였다.

private Tuple<string, ConsoleColor> _shopMsg;

먼저, 상점의 상단에 출력할 메세지를 저장할 튜플을 하나 선언하였다.

여기엔 어떤 물품의 구매를 성공했는지, 혹은 소지 금액이 모자라서 실패했는지 등을 알려주는 문자열이 저장된다.

item2가 출력할 색상이다.

 

아래는 상점 코드이다.

더보기
private void SelectShopItem()
{
    DevFunctions.WriteLineColored("구매하고 싶은 물건의 번호를 입력하세요.", ConsoleColor.Cyan);
    Console.WriteLine(); 

    Console.Write("1. 사과 : ");
    DevFunctions.WriteColored("30원", ConsoleColor.Red);
    DevFunctions.WriteLineColored("[사용시 HP를 50 회복합니다.]", ConsoleColor.Green);

    Console.Write("2. 배 : ");
    DevFunctions.WriteColored("50원", ConsoleColor.Red);
    DevFunctions.WriteLineColored("[사용시 HP를 100 회복합니다.]", ConsoleColor.Green);

    Console.Write("3. 필살의 영약 : ");
    DevFunctions.WriteColored("200원", ConsoleColor.Red);
    DevFunctions.WriteLineColored("[사용시 다음 공격의 크리티컬 확률이 50% 증가합니다.]", ConsoleColor.Green);

    Console.WriteLine("4. 구매를 종료합니다.");

    string input = Console.ReadLine();

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

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

    int toInt = int.Parse(input);

    switch (toInt)
    {
        case 1:
            BuyItem(ItemName.사과);
            break;
        case 2:
            break;
        case 3:
            break;
        case 4:
            _updateFunc = null;
            _selectFunc = new SelectFunc(SelectMenu);
            _shopMsg = null;
            break;
    }
}

물건을 선택하면 BuyItem이라는 함수를 호출한다.

더보기
 private void BuyItem(ItemName itemName)
 {
     if (Item.isCanBuy(_parentCore.CurrentPlayer, itemName) == true)
     {
         SetShopMsg_Buy(itemName);
         Item.BuyItem(_parentCore.CurrentPlayer, itemName);
     }
     else
     {
         SetShopMsg_Failed();
     }
 }
 
 private void SetShopMsg_Buy(ItemName itemName)
{
    _shopMsg = new Tuple<string, ConsoleColor>(itemName.ToString() + "를 구매하였습니다", ConsoleColor.Blue);
}

private void SetShopMsg_Failed()
{
    _shopMsg = new Tuple<string, ConsoleColor>("소지 금액이 모자랍니다.", ConsoleColor.Red);
}

레벨에 함수는 이렇게 설정되어 있고, 내부 코드를 보면 Item 클래스의 static 함수를 사용하고 있는데 코드는 아래와 같다.

더보기
public static bool isCanBuy(Player player, ItemName itemName)
{
    switch(itemName)
    {
        case ItemName.사과:
            return (player.Money >= 30);
    }

    return false;
}

public static void BuyItem(Player player, ItemName itemName)
{
    switch (itemName)
    {
        case ItemName.사과:
            player.Inventory.Add(new Apple());
            player.Money -= 30;
            break;
    }
}

물품을 구매할 수 있는지 확인하는 함수와 실제로 구매하는 함수이다.

 

아래 코드를 통해, 상단에 소지 금액과 함께 메세지가 출력된다. 그 아래엔 위에서 본 선택 메세지가 출력되는 것이다.

더보기
private void WriteShopMsg()
{
    string Str = "소지 금액 : " + _parentCore.CurrentPlayer.Money.ToString();
    DevFunctions.WriteLineColored(Str, ConsoleColor.Yellow);

    if (_shopMsg != null)
    {
        DevFunctions.WriteLineColored(_shopMsg.Item1, _shopMsg.Item2);
    }
}

 

구현된 결과다.

 

또한, 아이템을 구매한 뒤 현재 소지 품목을 출력하는 기능도 추가하였다.

물품을 구매한 뒤 2번 인벤토리를 확인한다를 선택하면 아래와 같이 소지 품목이 출력된다.

 

상점 기능을 추가하였고, 전투 기능을 추가하기 위해 몬스터와 플레이어에 공통의 인터페이스를 하나 상속하였다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace C_Study
{
    public interface ICreatureStat
    {
        int AttPower { get; }
        int DefPower { get; }
    }
}

그냥 공격력, 방어력을 get하는 함수만 있다.

 

이걸 상속하도록 한 이유는 데미지 계산을 하기 위해서인데, 공격자와 피격자가 몬스터인지 플레이어인지 알 필요 없이 공격력과 방어력만으로 계산하기 위해서이다.

public static int GetFinalAttPower(ICreatureStat attacking, ICreatureStat attacked)
{
    int Att = attacking.AttPower;
    int Def = attacked.DefPower;
    
    float DefRate = Def / 100.0f;
    DefRate = 1.0f - DefRate;
    
    return (int)(Att * DefRate);
}

위의 함수를 호출하면, 공격자가 몬스터인지 플레이어인지 알 필요 없이 결과적인 데미지만 얻어낼 수 있다.

이 외에도 필요한 기능이 있으면 인터페이스에 추가하면 될 것 같다.

 

생각해보니 아이템을 구현할 때 enum을 한글로 사용해보았다.

 public enum ItemName
 {
     사과,
     배,
     필살의영약,
 }

그냥 되나 궁금해서 해봤는데 되길래 걍 써봤다 ㅋㅋ

은근 편한 부분이 있긴 한데, 다시는 쓸 일 없을듯

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

 

먼저 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++의 문법에 점점 익숙해지는중..

'프로젝트 > C# 머드게임' 카테고리의 다른 글

C# 머드 게임 프로젝트 (4)  (0) 2024.08.01
C# 머드 게임 프로젝트 (3)  (0) 2024.07.30
C# 머드 게임 프로젝트 (1)  (0) 2024.07.28

C# 공부를 좀 재밌게 할 수 있는 방법 없나 찾아보다가 문득 머드게임이 떠올랐다.

머드게임을 즐겨 하던 세대는 아니지만, 머드게임을 구현하면서 C#을 공부하면 참 재밌을 것 같았다.

 

이름은 전사의 모험 RPG로 간단하게 정해봤다.

그냥 무난하게 턴제 형식의 전투를 만들 생각이다.

 

using C_Study;
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

class MainClass
{
    public static void Main()
    {
        GameCore gameCore = new GameCore();
        
        gameCore.Start();
        gameCore.Update();
        gameCore.End();
    }
}

GameCore라는 클래스 내부에서 게임을 전체적으로 관리할 것이다.

 

Main함수에선 인스턴스의 Start, Update, End를 호출하도록 하였다.

Start에선 게임이 시작되기 전에 설정해야 할 것들을 세팅하는 함수이고 Update는 게임이 진행되는 함수이다.

End는 게임이 종료될 때 해야 할 일을 하는 함수이다.

 

아래는 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,
        Play,
    }

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

        public void Update()
        {
            while(_player != null)
            {
                switch (_levelType)
                {
                    case LevelType.Start:
                        StartLevelUpdate();
                        break;
                    case LevelType.Play:
                        PlayLevelUpdate();
                        break;
                }
            }
        }

        public void End()
        {

        }

        private void StartLevelUpdate()
        {
            while (_levelType == LevelType.Start)
            {
                Console.Clear();

                Console.WriteLine("*************************************************");
                Console.WriteLine("*************************************************");
               
                Console.Write("*****************");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write("전사의 모험 RPG");
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine("*****************");

                Console.Write("******************");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write("제작자 오의현");
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine("******************");

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

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

                Console.ForegroundColor = ConsoleColor.Blue;
                Console.WriteLine("1 : YES");
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("2 : NO");
                Console.ForegroundColor = ConsoleColor.White;

                string input = Console.ReadLine();

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

                if(input.Length > 1)
                {
                    continue;
                }
                
                int toInt = int.Parse(input);

                switch (toInt)
                {
                    case 1:
                        _levelType = LevelType.Play;
                        break;
                    case 2:
                        Environment.Exit(0);
                        break;
                }
            }
        }
        private void PlayLevelUpdate()
        {
            while (_levelType == LevelType.Play)
            {
                Console.Clear();

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

                string input = Console.ReadLine();

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

                int toInt = int.Parse(input);
            }
        }

        private Player _player;
        private LevelType _levelType;
    }
}

 

Start에선 일단 플레이어 생성과 최초 레벨 설정만 해주었다.

Update에선 Level에 따라 다른 함수가 실행되도록 하였다.

 

중간에 DevFunctions.IsNumeric()이라는 함수가 보이는데 이는 입력받은 문자열이 정수로만 이루어져있는지 판단하는 함수이다. 

 

내부 코드는 아래와 같다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace C_Study
{
    public class DevFunctions
    {
        public static bool IsNumeric(string str)
        {
            if (string.IsNullOrEmpty(str) == true)
            {
                return false;
            }

            foreach (char curChar in str)
            {
                if (char.IsDigit(curChar) == false)
                {
                    return false;
                }
            }

            return true;
        }
    }
}

 

StartLevel은 게임시작 화면이다.

이런 식으로 화면에 출력된다. 시작 창을 화려하게 꾸며보고 싶었는데, 그런 쪽에는 재능이 없는지라 심플하게 갔다.

1번을 누르면 게임이 시작되고 2번을 누르면 콘솔이 종료된다.

 

PlayLevel을 구현하기 전에 무기 클래스를 먼저 구현해놓았다. 무기 클래스 코드는 아래와 같다.

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()
        {

        }

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

        protected string WPName
        {
            get { return _wpName; }
            set { _wpName = value; }
        }
        protected int AttPower
        {
            get { return _attPower; }
            set { _attPower = value; }
        }
        protected float BonusAttackProb
        {
            get { return _bonusAttackprob; }
            set { _bonusAttackprob = value; }
        }

        private int _attPower;
        private float _bonusAttackprob;

        private string _wpName;
        private WeaponType _wpType;
    }

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

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

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

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

    public class OldSpear : Spear
    {
        public OldSpear()
        {
            WPType = WeaponType.Spear;
            WPName = "오래된 창";
            AttPower = 15;
        }
    }

    public class OldHammer : Hammer
    {
        public OldHammer()
        {
            WPType = WeaponType.Hammer;
            WPName = "오래된 망치";
            AttPower = 30;
        }
    }
}

 

Weapon 클래스를 Spear, Sword, Hammer 클래스가 상속받도록 하였고 실제로 플레이어가 장착할 무기들은 종류에 맞는 클래스를 상속받도록 하였다.

 

코드의 아래쪽에 보면 OldSword, OldSpear, OldHammer가 있는데 이게 플레이어가 실제 장착할 아이템이다.

처음에 장착할 기본템이며 일단은 이 3개만 만들어두었다.

 

무기는 2개의 스탯을 가지고 있다. 공격력과 추가공격확률이다.

 

공격력은 말 그대로 공격력이고, 추가공격확률은 공격시에 추가적으로 공격이 발생할 확률이다.

검은 공격력이 낮은 대신 추가공격확률이 높고, 망치는 공격력이 높은 대신 추가공격확률이 낮다.

창은 그 중간쯤이다.

 

일단은 이정도만 구현해보았다. 

다음엔 게임 플레이 내용을 본격적으로 구현할 것이다.

+ Recent posts