21. 팀 프로젝트 과제(4)

이규성·2023년 11월 23일
0

TIL

목록 보기
26/106

11/22 팀 프로젝트의 마무리

📌알고리즘 코드 카타 (Algorithm Code Kata)

평균 구하기

정수를 담고 있는 배열 arr의 평균값을 return하는 함수, solution을 완성해보세요.

입출력 예

arrreturn
[1,2,3,4]2.5
[5,5]5
public class Solution 
{
    public double solution(int[] arr) 
    {
        int i = 0;
        double answer = 0;

        while (i < arr.Length)
        {
            answer += arr[i];
            i++;                                        
        }
        return answer / arr.Length;
    }
}

이전에 풀었던 알고리즘과 비슷한 문제였어서 어렵지 않게 해결할 수 있었다.

📌팀 프로젝트

BattleScene에서 생성한 몬스터의 값들이 다른 Scene으로 전달이 되지 않아서 갖은 시도를 해보았지만 시간 관계상 튜터님의 해설 강의를 보며 필수 요소들은 구현에 성공하였다.

우선 몬스터 데이터의 저장 방법과 랜덤 생성에서 변화가 있었다.

// Monster.cs
public class Monster : ICloneable // ICloneable 인터페이스의 사용
{
    public string MonsterName { get; }
    public string Description { get; }
    public int Atk { get; set; }
    public int Def { get; set; }
    public int Hp { get; set; }
    public int Mp { get; set; }
    public static int MonsterCnt = 0;
    public bool IsDead { get; set; }

    public Monster(string monstername, string description, int atk, int def, int hp, int mp,
    bool isDead = false)
    {
        MonsterName = monstername;
        Description = description;
        Atk = atk;
        Def = def;
        Hp = hp;
        Mp = mp;
        IsDead = isDead;
    }        
    public object Clone() // 복제를 위한 Clone() 메서드 생성
    {
        return MemberwiseClone();
    }
}
// GameData.cs
public static Monster[] monsters;

public static void GameDataSetting()
{
    monsters = new Monster[] // 메서드를 사용하여 몬스터 정보를 저장하였으나 즉시 초기화로 변경함
    {
    new Monster("가고일", "마계에서 온 흔히 볼 수 있는 마물입니다.", 12, 10, 40, 10),
    new Monster("골렘", "마기가 가득한 돌덩이들이 모여 만들어졌습니다.", 5, 10, 60, 10),
    new Monster("고블린", "마계에서 군락을 이루고 사는 하급 마물입니다.", 8, 10, 30, 10)
    };
}
// Scene.cs
private static int monsterCnt;
private static List<Monster> monsterList;

public static void InitializeMonsterList()
{
    Random random = new Random();
    monsterCnt = random.Next(1, 5);
    monsterList = new List<Monster>(monsterCnt);

    for (int i = 0; i < monsterCnt; i++)
    {
        int monsterIdx = random.Next(0, GameData.monsters.Length);
        Monster monsterData = GameData.monsters[monsterIdx];
        monsterList.Add(monsterData.Clone() as Monster); 
        // 몬스터의 오리지날 정보는 건드리지 않기 위해 클론으로 복사하여 리스트에 저장
    }
}

전체적인 골자는 몬스터의 오리지날 데이터를 배열에 저장하고, 클론으로 복제 몬스터를 리스트에 저장해놓고 리스트를 호출하여서 사용한다.

// 몬스터 랜덤 생성 메서드
private static void RandomMonsterCount(bool withNumber = false)
{
    for (int i = 0; i < monsterList.Count; i++)
    {
        if (monsterList[i].IsDead) // 몬스터의 앞에 - 출력
        {
            Console.ForegroundColor = ConsoleColor.DarkGray;
            Console.Write("- ");
            Console.ResetColor();
        }
        else
        {
            Console.Write("- ");
        }

        if (withNumber) // 플레이어 공격 턴에 몬스터 앞에 번호를 붙여서 공격 대상으로 지정
        {
            if (monsterList[i].IsDead)
            {
                Console.ForegroundColor = ConsoleColor.DarkGray;
                Console.Write($"{i + 1}. ");
                Console.ResetColor();
            }
            else
            {
                Console.Write($"{i + 1}. ");
            }
        }

        if (monsterList[i].IsDead) // 몬스터가 죽으면 이름을 회색으로 바꿔준다.
        {
            Console.ForegroundColor = ConsoleColor.DarkGray;
            Console.Write($"{monsterList[i].MonsterName} ");
            Console.ResetColor();
        }
        else
        {
            Console.Write($"{monsterList[i].MonsterName} ");
        }

        Console.Write(" | ");
        Console.WriteLine(monsterList[i].IsDead ? "Dead" : $"Hp {monsterList[i].Hp}");
        // 몬스터가 죽으면 Dead, 아니면 Hp 출력
    }
}

InitializeMonsterList();
RandomMonsterCount();
순서로 호출하고 다른 Scene에서는 RandomMonsterCount();만 호출하여 사용한다. 그럼 초기에 생성된 몬스터들이 그대로 전달이 된다.
클론을 이용해 복제 몬스터를 사용하지 않는다면 중복된 몬스터가 생성되었을 시 한 개체를 공격하면 다른 개체의 데이터에도 영향을 끼치게 된다.

플레이어와 몬스터 상호 간의 데미지 구현에도 수정이 있었다.

public static int PlayerAtkDamage()
{
    // 아이템을 장착하면 캐릭터의 스탯에 반영한다.
    double MinDamage = (GameData.GetSumBonusAtk() > 0 ? 
    GameData.player.Atk + GameData.GetSumBonusAtk() : 
    GameData.player.Atk) - Math.Ceiling((double)GameData.player.Atk / 10);
    
    double MaxDamage = (GameData.GetSumBonusAtk() > 0 ? 
    GameData.player.Atk + GameData.GetSumBonusAtk() : 
    GameData.player.Atk) + Math.Ceiling((double)GameData.player.Atk / 10);
    
    int atkDamage = new Random().Next((int)MinDamage, (int)MaxDamage + 1);
    return atkDamage;
}
public static void PlayerTakeDamage(int damage, int i)
{
    GameData.player.Hp -= damage;
    if (GameData.player.Hp <= 0)
    {
        GameData.player.IsDead = true;
        GameData.player.Hp = 0;
        
        Console.WriteLine($"{GameData.player.Name}() {monsterList[i].MonsterName}에게 
        {damage}의 데미지를 받았습니다. 남은 체력: {GameData.player.Hp}\n");
        
        Console.WriteLine($"{GameData.player.Name}이(가) 죽었습니다.");
    }
    else
    {
        Console.WriteLine($"{GameData.player.Name}() {monsterList[i].MonsterName}에게 
        {damage}의 데미지를 받았습니다. 남은 체력: {GameData.player.Hp}\n");
    }
}
public static int MonsterAtkDamage(int i)
{
    double MinDamage = monsterList[i].Atk - Math.Ceiling((double)monsterList[i].Atk / 10);
    double MaxDamage = monsterList[i].Atk + Math.Ceiling((double)monsterList[i].Atk / 10);
    int atkDamage = new Random().Next((int)MinDamage, (int)MaxDamage + 1);
    return atkDamage;
}
public static void MonsterTakeDamage(int damage, int i)
{
    monsterList[i].Hp -= damage;

    if (monsterList[i].Hp <= 0)
    {
        monsterList[i].Hp = 0;
        monsterList[i].IsDead = true;
        
        Console.WriteLine($"{monsterList[i].MonsterName}() {damage}의 데미지를 받았습니다. 
        남은 체력: {monsterList[i].Hp}");
        
        Console.WriteLine($"{monsterList[i].MonsterName}이(가) 죽었습니다.");
    }
    else
    {
        Console.WriteLine($"{monsterList[i].MonsterName}() {damage}의 데미지를 받았습니다. 
        남은 체력: {monsterList[i].Hp}");
    }
}

전체적으로 몬스터리스트 내부의 복제 몬스터의 데이터를 호출하도록 바뀌었다.

플레이어의 공격 턴

private static bool isAllMonsterDead => monsterList.All((o) => o.IsDead == true);

링큐를 사용하여 몬스터의 사망을 판단한다.

public static void PlayerAtkScene()
{
    Console.Clear();
    RandomMonsterCount(true); // 몬스터의 앞에 숫자를 붙여준다.

    Console.WriteLine();
    
    Console.WriteLine();
    Console.WriteLine("공격할 몬스터의 번호를 선택하세요. ");
    Console.WriteLine();
    Console.WriteLine("0. 마을로 돌아가기 ");
    Console.WriteLine();

    int inputIdx;
    do
    {    // monsterCnt는 0부터 시작하므로 키보드 입력값에서 1을 빼준다.
        inputIdx = Program.CheckValidInput(0, monsterCnt) - 1;
        
        if (inputIdx < 0) // 0을 입력하면 마을로 가기 위함
        {
            DisplayTown();
            break;
        }
    }
    while (monsterList[inputIdx].IsDead == true); // 몬스터가 모두 

    MonsterTakeDamage(PlayerAtkDamage(), inputIdx);

    Console.WriteLine("\n아무 키 입력 시 다음 턴으로 넘어갑니다. ");
    Console.ReadKey();

    if (isAllMonsterDead)
    {
        PlayerWin();
    }
    else
    {
        MonsterAtkScene();
    }
}

코드가 너무 길어져서 핵심만 간추렸다.

몬스터의 공격 턴

public static void MonsterAtkScene()
{
    Console.Clear();

    RandomMonsterCount(true);

    Console.WriteLine();
    Console.WriteLine("몬스터가 공격합니다. ");
    Console.WriteLine();

    int idx = 0;

    for (int i = 0; i < monsterCnt; i++)
    {
        if (monsterList[i].IsDead)
        {
            continue;
        }
        else
        {
            PlayerTakeDamage(MonsterAtkDamage(i), i);
            Thread.Sleep(1000); // 몬스터의 공격 사이에 딜레이를 준다.
        }

        if (GameData.player.IsDead)
        {
            Console.WriteLine("\n아무 키 입력 시 다음 턴으로 넘어갑니다. ");
            Console.ReadKey();
            PlayerLose();                                        
        }                
    }
    Console.WriteLine("\n아무 키 입력 시 다음 턴으로 넘어갑니다. ");
    Console.ReadKey();
    PlayerAtkScene();
}

🤸🏻‍♀️Feedback

팀 프로젝트를 마무리하여, , ,
주어진 시간이 너무 짧아서 결과물에 아쉬움이 많이 남는다. 그래도 기본 기능은 구현이 되었고, 다른 분이 선택 사항도 구현을 해주셔서 제출에는 성공하였다.
발생한 문제들

  • 깃, 깃허브 사용이 어려웠다.
    깃허브 강의를 해주시긴 했지만 설치나 커밋, pull, push 방법 그리고 메인과 브랜치 등의 기본적인 개념과 사용법만 알려주셔서 실제로 협업에는 어떻게 사용해야 하는지는 직접 부딪혀서 알아낸 것이 많다.
  • 업무 분담이 어려웠다.
    와이어프레임으로 씬들을 정리하고 어떤 메서드가 필요한지 정리한 것 까지는 좋았지만 각자 어떤 파트를 구현을 할지 정하는데에도 시간이 소요되었지만, 파트가 나뉘었음에도 씬과 메서드 사이에 참조하는 값들이 있다보니 결국 같은 구현을 여럿이서 하는 문제가 발생하였다.
  • 팀원들의 성장이 고르지 않았다고 생각한다..
    위와 이어지는 내용인데 결국에는 시간이 부족하여 몬스터의 랜덤 생성, 각종 씬들, 상호 간의 공격, 사망 시 게임오버 호출 등을 나 혼자서 그냥 독단으로 구현해서 팀원분들의 양해를 구하여 커밋하였다. 시간이 조금만 더 있었어도 다같이 구현한 코드를 놓고 회의를 할 수 있었을텐데 이건 그냥 만들어서 던진 격이라 죄송스러운 마음이 컸다.

0개의 댓글