16. C# 문법 종합반 3주차(3)

이규성·2023년 11월 15일
0

TIL

목록 보기
21/106

11/15 3주차 과제인 뱀 게임, 블랙잭 게임을 구현해 보자.

📌뱀 게임

목표 예시


자동으로 움직이는 뱀이 생성되고, 랜덤으로 생성되는 음식과 닿으면 뱀의 몸 길이가 길어진다. 자신의 몸에 닿거나 벽에 닿으면 게임 오버 ! 뱀의 길이가 길어질 수록 게임의 속도가 빨라진다.

필요한 클래스

  • Point: 뱀을 그릴 char '*'에 대한 정보를 저장한다.
  • Snake: 뱀의 상태 즉, 이동, 음식 벽 그리고 자신의 몸에 충돌 판단의 정보를 저장한다.
  • FoodCreator: 설정한 맵의 범위 안에서 랜덤으로 음식을 생성하는 정보를 저장한다.

필요한 Console 메서드 기능

  • Console.KeyAvailable: 키보드의 입력을 받는다.
  • Console.ReadKey(): 키보드로 입력한 기능 키를 인식하여 가져온다. ( ) 안에 bool값을 넣어서 콘솔창에 표시할지 지정할 수 있다.
  • ConsoleKey.LeftArrow: 키보드의 왼쪽 화살표 기능 키, Up, Down, Right 도 마찬가지

맵 그리기

static void DrawWall() 
// x값이 2씩 변하여 좌측 벽에 닿으니 오류가 일어났다. 맵의 x값을 0부터가 아닌 5부터로 바꾸어서 해결
{
    for (int i = 5; i < 85; i++)
    {
        Console.SetCursorPosition(i, 0);
        Console.WriteLine("#");
        Console.SetCursorPosition(i, 20);
        Console.WriteLine("#");
    }

    for (int i = 0; i < 20; i++)
    {
        Console.SetCursorPosition(5, i);
        Console.WriteLine("#");
        Console.SetCursorPosition(85, i);
        Console.WriteLine("#");
    }
}

예제 코드의 맵의 시작 x값이 0부터 였는데 나중에 나올 뱀의 이동 x값과 충돌하여 임의로 수정하였다.

Point 클래스

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }
    public char Sym { get; set; }

    public Point(int x, int y, char sym)
    {
        X = x;
        Y = y;
        Sym = sym;
    }
    public void Draw() // '*'을 그려주는 메서드
    {
        Console.SetCursorPosition(X, Y);
        Console.Write(Sym);
    }
    public void Clear() // '*'이 무한정 그려지는 것을 방지하는 메서드
    {
        Sym = ' ';
        Draw();
    }
    public bool IsHit(Point p) // 뱀의 충돌 여부를 판단하는 메서드
    {
        return p.X == X && p.Y == Y;
    }
}

'*'과 관련된 정보들을 저장해 준다.

4방향 지정

public enum _4WayDirection
{
    LEFT,
    RIGHT,
    UP,
    DOWN
}

Snake 클래스

public class Snake
{
    // 뱀의 상태, 이동
    // 음식 먹기
    // 자신의 몸에 부딪혔는지 판단
    public List<Point> body; // 뱀의 몸통을 리스트로 표현합니다.
    public _4WayDirection Direction; // 뱀의 현재 방향을 저장합니다.

    public Snake(Point tail, int length, _4WayDirection direction)
    // 뱀의 몸길이, 시작 진행 방향 등의 정보를 저장한다.
    {
        Direction = direction;
        body = new List<Point>();
        for (int i = 0; i < length; i++)
        {
            Point p = new Point(tail.X, tail.Y, '*');
            body.Add(p);
            tail.X += 1;
        }

    }

    public void Draw()
    {
        foreach (Point p in body)
        {
            p.Draw();
        }
    }

    public void Move()
    {
        Point tail = body.First();
        body.Remove(tail);
        Point head = GetNextPoint();
        body.Add(head);

        tail.Clear();
        head.Draw();
    }

    public Point GetNextPoint() // 키보드 방향키 입력으로 뱀의 방향을 제어한다.
    {
        Point head = body.Last();
        Point nextPoint = new Point(head.X, head.Y, head.Sym);
        switch (Direction)
        {
            case _4WayDirection.LEFT:
                nextPoint.X -= 2;
                break;
            case _4WayDirection.RIGHT:
                nextPoint.X += 2;
                break;
            case _4WayDirection.UP:
                nextPoint.Y -= 1;
                break;
            case _4WayDirection.DOWN:
                nextPoint.Y += 1;
                break;
        }
        return nextPoint;
    }
    public bool IsHitTail() // 자신의 몸과 닿았는지 판단하는 메서드
    {
        var head = body.Last();
        for (int i = 0; i < body.Count - 2; i++)
        {
            if (head.IsHit(body[i]))
            {
                return true;
            }                        
        }
        return false;
    }

    public bool IsHitWall() // 벽과 닿았는지 판단하는 메서드
    {
        var head = body.Last();
        if (head.X <= 5 || head.X >= 85 || head.Y <= 0 || head.Y >= 20)
        {
            return true;
        }
        else
        {
            return false;
        }

    }

    public bool Eat(Point food) // 음식과 닿았는지 판단하는 메서드
    {
        Point head = GetNextPoint();
        if (head.IsHit(food))
        {
            food.Sym = head.Sym;
            body.Add(food);
            return true;
        }
        else
        {
            return false;
        }
    }
}

뱀에게 필요한 정보들을 적절한 메서드로 만들어 준다.

FoodCreator 클래스

public class FoodCreator
{
    // 설정한 맵의 범위 안에서 랜덤으로 음식이 생성
    int mapWidth;
    int mapHeight;
    char sym;

    Random random = new Random();

    public FoodCreator(int mapWidth, int mapHeight, char sym)
    {
        this.mapWidth = mapWidth;
        this.mapHeight = mapHeight;
        this.sym = sym;
    }
    public Point CreateFood() // 맵의 범위 안에 들어오도록 설정해 준다.
    {
        int x = random.Next(8, mapWidth - 2);
        // x 좌표를 2단위로 맞추기 위해 짝수로 만듭니다.
        x = x % 2 == 1 ? x : x + 1;
        int y = random.Next(2, mapHeight - 2);
        return new Point(x, y, sym);
    }
}

Main 내부에 넣을 정보들

 static void Main(string[] args)
 {
     // 뱀의 이동
     // 음식 먹기
     // 게임오버 조건
     int gameSpeed = 100; // 게임 속도 제어
     int foodCount = 0; // 먹은 음식 정보

     DrawWall();

     Point p = new Point(10, 5, '*'); // 맵크기 변경으로 인해 뱀의 생성 x 값 수정
     Snake snake = new Snake(p, 4, _4WayDirection.RIGHT);
     snake.Draw();

     // 음식의 위치를 무작위로 생성하고, 그립니다.
     FoodCreator foodCreator = new FoodCreator(86, 20, '$');
     Point food = foodCreator.CreateFood();
     food.Draw();

     while (true) // 게임오버 조건이 만족하지 않을 경우 계속 반복 실행
     {
         if (Console.KeyAvailable) // 키보드의 입력이 있어야 내용을 실행한다.
         {                    
             var key = Console.ReadKey(true).Key;

             switch (key)
             {
                 case ConsoleKey.LeftArrow:
                     snake.Direction = _4WayDirection.LEFT;
                     break;

                 case ConsoleKey.RightArrow:
                     snake.Direction = _4WayDirection.RIGHT;
                     break;

                 case ConsoleKey.UpArrow:
                     snake.Direction = _4WayDirection.UP;
                     break;

                 case ConsoleKey.DownArrow:
                     snake.Direction = _4WayDirection.DOWN;
                     break;
             }
         }

         // 뱀이 음식을 먹었는지 확인합니다.
         if (snake.Eat(food))
         {
             foodCount++; // 먹은 음식 수를 증가
             food.Draw();

             // 뱀이 음식을 먹었다면, 새로운 음식을 만들고 그립니다.
             food = foodCreator.CreateFood();
             food.Draw();
             if (gameSpeed > 10) // 게임이 점점 빠르게
             {
                 gameSpeed -= 10;
             }
         }
         else
         {
             // 뱀이 음식을 먹지 않았다면, 그냥 이동합니다.
             snake.Move();
         }

         if (snake.IsHitWall() || snake.IsHitTail())
         {
             break;
         }
         Thread.Sleep(gameSpeed);

         Console.SetCursorPosition(6, 22); // 커서 위치 설정
         Console.WriteLine($"먹은 음식 수: {foodCount}"); // 먹은 음식 수 출력
     }
     WriteGameOver();
     Console.ReadLine();
 }
 static void WriteGameOver()
 {
     Console.SetCursorPosition(36, 10);
     Console.WriteLine("G A M E O V E R !");
 }

전체 코드

namespace _3_Week_Assignment
{
   internal class Program
   {
       static void Main(string[] args)
       {
           // 뱀의 이동
           // 음식 먹기
           // 게임오버 조건
           int gameSpeed = 100;
           int foodCount = 0;

           DrawWall();

           Point p = new Point(10, 5, '*'); // 맵크기 변경으로 인해 뱀의 생성 x 값 수정
           Snake snake = new Snake(p, 4, _4WayDirection.RIGHT);
           snake.Draw();

           // 음식의 위치를 무작위로 생성하고, 그립니다.
           FoodCreator foodCreator = new FoodCreator(86, 20, '$');
           Point food = foodCreator.CreateFood();
           food.Draw();

           while (true)
           {
               if (Console.KeyAvailable)
               {                    
                   var key = Console.ReadKey(true).Key;

                   switch (key)
                   {
                       case ConsoleKey.LeftArrow:
                           snake.Direction = _4WayDirection.LEFT;
                           break;

                       case ConsoleKey.RightArrow:
                           snake.Direction = _4WayDirection.RIGHT;
                           break;

                       case ConsoleKey.UpArrow:
                           snake.Direction = _4WayDirection.UP;
                           break;

                       case ConsoleKey.DownArrow:
                           snake.Direction = _4WayDirection.DOWN;
                           break;
                   }
               }

               // 뱀이 음식을 먹었는지 확인합니다.
               if (snake.Eat(food))
               {
                   foodCount++; // 먹은 음식 수를 증가
                   food.Draw();

                   // 뱀이 음식을 먹었다면, 새로운 음식을 만들고 그립니다.
                   food = foodCreator.CreateFood();
                   food.Draw();
                   if (gameSpeed > 10) // 게임이 점점 빠르게
                   {
                       gameSpeed -= 10;
                   }
               }
               else
               {
                   // 뱀이 음식을 먹지 않았다면, 그냥 이동합니다.
                   snake.Move();
               }

               if (snake.IsHitWall() || snake.IsHitTail())
               {
                   break;
               }
               Thread.Sleep(gameSpeed);

               Console.SetCursorPosition(6, 22); // 커서 위치 설정
               Console.WriteLine($"먹은 음식 수: {foodCount}"); // 먹은 음식 수 출력
           }
           WriteGameOver();
           Console.ReadLine();
       }
       static void WriteGameOver()
       {
           Console.SetCursorPosition(36, 10);
           Console.WriteLine("G A M E O V E R !");
       }
       static void DrawWall() // x값이 2씩 변하여 좌측 벽에 닿으니 오류가 일어났다. 
       // 맵의 x값을 0부터가 아닌 5부터로 바꾸어서 해결
       {
           for (int i = 5; i < 85; i++)
           {
               Console.SetCursorPosition(i, 0);
               Console.WriteLine("#");
               Console.SetCursorPosition(i, 20);
               Console.WriteLine("#");
           }

           for (int i = 0; i < 20; i++)
           {
               Console.SetCursorPosition(5, i);
               Console.WriteLine("#");
               Console.SetCursorPosition(85, i);
               Console.WriteLine("#");
           }
       }

       public class Point
       {
           public int X { get; set; }
           public int Y { get; set; }
           public char Sym { get; set; }

           public Point(int x, int y, char sym)
           {
               X = x;
               Y = y;
               Sym = sym;
           }
           public void Draw()
           {
               Console.SetCursorPosition(X, Y);
               Console.Write(Sym);
           }
           public void Clear()
           {
               Sym = ' ';
               Draw();
           }
           public bool IsHit(Point p)
           {
               return p.X == X && p.Y == Y;
           }
       }
       public enum _4WayDirection
       {
           LEFT,
           RIGHT,
           UP,
           DOWN
       }

       public class Snake
       {
           // 뱀의 상태, 이동
           // 음식 먹기
           // 자신의 몸에 부딪혔는지 판단
           public List<Point> body; // 뱀의 몸통을 리스트로 표현합니다.
           public _4WayDirection Direction; // 뱀의 현재 방향을 저장합니다.

           public Snake(Point tail, int length, _4WayDirection direction)
           {
               Direction = direction;
               body = new List<Point>();
               for (int i = 0; i < length; i++)
               {
                   Point p = new Point(tail.X, tail.Y, '*');
                   body.Add(p);
                   tail.X += 1;
               }

           }

           public void Draw()
           {
               foreach (Point p in body)
               {
                   p.Draw();
               }
           }

           public void Move()
           {
               Point tail = body.First();
               body.Remove(tail);
               Point head = GetNextPoint();
               body.Add(head);

               tail.Clear();
               head.Draw();
           }

           public Point GetNextPoint()
           {
               Point head = body.Last();
               Point nextPoint = new Point(head.X, head.Y, head.Sym);
               switch (Direction)
               {
                   case _4WayDirection.LEFT:
                       nextPoint.X -= 2;
                       break;
                   case _4WayDirection.RIGHT:
                       nextPoint.X += 2;
                       break;
                   case _4WayDirection.UP:
                       nextPoint.Y -= 1;
                       break;
                   case _4WayDirection.DOWN:
                       nextPoint.Y += 1;
                       break;
               }
               return nextPoint;
           }
           public bool IsHitTail()
           {
               var head = body.Last();
               for (int i = 0; i < body.Count - 2; i++)
               {
                   if (head.IsHit(body[i]))
                   {
                       return true;
                   }                        
               }
               return false;
           }

           public bool IsHitWall()
           {
               var head = body.Last();
               if (head.X <= 5 || head.X >= 85 || head.Y <= 0 || head.Y >= 20)
               {
                   return true;
               }
               else
               {
                   return false;
               }

           }

           public bool Eat(Point food)
           {
               Point head = GetNextPoint();
               if (head.IsHit(food))
               {
                   food.Sym = head.Sym;
                   body.Add(food);
                   return true;
               }
               else
               {
                   return false;
               }
           }
       }

       public class FoodCreator
       {
           // 설정한 맵의 범위 안에서 랜덤으로 음식이 생성
           int mapWidth;
           int mapHeight;
           char sym;

           Random random = new Random();

           public FoodCreator(int mapWidth, int mapHeight, char sym)
           {
               this.mapWidth = mapWidth;
               this.mapHeight = mapHeight;
               this.sym = sym;
           }
           public Point CreateFood()
           {
               int x = random.Next(8, mapWidth - 2);
               // x 좌표를 2단위로 맞추기 위해 짝수로 만듭니다.
               x = x % 2 == 1 ? x : x + 1;
               int y = random.Next(2, mapHeight - 2);
               return new Point(x, y, sym);
           }
       }
       
   }
}

🤸🏻‍♀️Feedback

처음에 과제를 받았을 땐, 그 시커먼 콘솔창에서 뱀이 움직이며 점점 늘어난다는 것 자체가 상상이 잘가지가 않았어서 해설 코드를 보면서도 의아했었다. 하지만 필요한 클래스들을 먼저 나열해놓고 정보들과 기능들을 하나하나 추가하고 디버그를 통해 흐름을 익혀 나갔고 적절하게 내 입맛대로 수정하여 완성할 수 있었다.

📌블랙잭 게임

목표 예시


고전적인 카드 게임이다. 딜러와 플레이어 각각 카드를 두 장 가지고 시작하며, 두 장의 합이 21이 되거나 가까운 사람의 승리 ! 21을 초과하면 패배이다. 플레이어는 21에 가까워 지기 위해서 계속해서 카드를 받을 수 있지만, 딜러는 카드의 합이 17 미만이라면 무조건 카드를 받아야 한다.

필요한 클래스

  • Card: 트럼프 카드의 속성을 정의하고 저장한다.
  • Deck: 카드를 52장 생성, 랜덤으로 섞기, 등의 정보를 저장한다.
  • Hand: 손에 들어온 카드들의 정보를 저장한다. 카드의 합계 등
  • Player: Deck -> Hand -> Player 이렇게 카드를 전달하는 정보를 저장한다.
  • Dealer : Player: 플레이어 클래스에 종속된 클래스이며, 카드 합계가 17미만일 시 카드를 계속 뽑는 정보를 저장한다.
  • BlackJack: 블랙잭 게임의 룰에 대한 정보를 저장한다.

트럼프 카드의 정보

public enum Suit { Hearts, Diamonds, Clubs, Spades } // 카드의 문양
public enum Rank { Two = 2, Three, Four, Five, Six, Seven, Eight, Nine, 
Ten, Jack, Queen, King, Ace } // 카드의 숫자 정보

트럼프 카드의 문양과 숫자에 대한 정보를 선언해 준다.

Card 클래스

public class Card
{        
    public Suit Suit { get; private set; }        
    public Rank Rank { get; private set; }

    public Card(Suit s, Rank r)
    {
        Suit = s;
        Rank = r;
    }

    public int GetValue() // 카드에 숫자 정보를 입혀서 반환한다.
    {
        if ((int)Rank <= 10) // Two ~ Ten 까진 각자 순서에 맞게
        {
            return (int)Rank;
        }
        else if ((int)Rank <= 13) // Jack ~ King 는 10점
        {
            return 10;
        }
        else
        {
            return 11; // Ace는 1점, 11점인데 우선 기본은 11점 반환
        }
    }

    public override string ToString() // 카드의 정보를 문자열로 변환
    {
        return $"{Rank} of {Suit}";
    }
}

Deck 클래스

public class  Deck
{
    // 카드가 들어있는 덱에 대한 정보
    private List<Card> cards;
    
    public Deck() // 카드에 대한 정보를 리스트로 저장하고, 트럼프 문양과 숫자를 연결시켜 준다.
    {
        cards = new List<Card>();

        foreach (Suit s in Enum.GetValues(typeof(Suit)))
        {
            foreach (Rank r in Enum.GetValues(typeof(Rank)))
            {
                cards.Add(new Card(s, r));
            }
        }
        Shuffle();
    }        
    public void Shuffle() // 카드들을 섞어주는 메서드
    {
        Random rand = new Random();

        for (int i = 0; i < cards.Count; i++)
        {
            int j = rand.Next(i, cards.Count);
            Card temp = cards[i];
            cards[i] = cards[j];
            cards[j] = temp;
        }
    }

    public Card DrawCard() // 카드를 뽑으면 덱에서 해당 카드를 삭제하는 메서드
    {
        Card card = cards[0];
        cards.RemoveAt(0);
        return card;
    }
}

Hand 클래스

public class Hand
{
    // 손에 들어간 카드에 대한 정보
    private List<Card> cards;

    public Hand() // 마찬가지로 카드를 리스트로 저장
    {
        cards = new List<Card>();
    }
    public void AddCard(Card card)
    {
        cards.Add(card);
    }
    public int GetTotalValue() // 카드 숫자를 합산하는 메서드
    {
        int total = 0;
        int aceCount = 0;

        foreach (Card card in cards)
        {
            if (card.Rank == Rank.Ace)
            {
                aceCount++;
            }
            total += card.GetValue();
        }

        // 에이스가 있고 총점이 21점을 넘을 때, 에이스를 1점으로 취급
        while (total > 21 && aceCount > 0)
        {
            total -= 10;
            aceCount--;
        }

        return total;
    }
}

Player 클래스

public class Player
{
    // 플레이어에 대한 정보
    public Hand Hand { get; private set; }

    public Player()
    {
        Hand = new Hand();
    }

    // 카드를 뽑는 메소드
    public Card DrawCardFromDeck(Deck deck)
    {
        Card drawnCard = deck.DrawCard();
        Hand.AddCard(drawnCard);
        return drawnCard;
    }
}

Dealer : Player 클래스

 public class  Dealer : Player // 기본적으로 플레이어 클래스의 정보와 같고 추가 정보가 있다
 {
     // 딜러에 대한 정보
     // 17점까지 카드를 계속 뽑게 하자
     public void KeepDrawingCards(Deck deck)
     {
         while (Hand.GetTotalValue() < 17)
         {
             Card drawnCard = DrawCardFromDeck(deck);
             Console.WriteLine($"딜러는 '{drawnCard}'() 뽑았습니다. 
             현재 총합은 {Hand.GetTotalValue()}점입니다.");
         }
     }
 }

BlackJack 클래스

public class BlackJack
{
    // 블랙잭 게임의 정보
    // 손에는 두 장의 카드가 있고 합이 21점에 가까우면 승리
    // 21점을 넘어가면 패배
    
    private Player player;
    private Dealer dealer;
    private Deck deck;        

    public void PlayGame()
    {            
        deck = new Deck();
        player = new Player();
        dealer = new Dealer();

        Console.WriteLine("게임을 시작합니다!\n");

        for (int i = 0; i < 2; i++) // 플레이어가 뽑은 카드를 보기 위해서 출력
        {
            Console.WriteLine(player.DrawCardFromDeck(deck));
        }

        Console.WriteLine($"플레이어의 초기 카드 합: {player.Hand.GetTotalValue()}\n");            

        Console.WriteLine($"딜러의 오픈 카드: {dealer.DrawCardFromDeck(deck)}\n"); 
        // 실제 블랙잭의 룰처럼 딜러의 카드 한 장만을 오픈
        dealer.DrawCardFromDeck(deck);


        // 플레이어의 차례, 21점이 넘지 않는다면 계속해서 카드를 뽑을 수 있다
        while (player.Hand.GetTotalValue() < 21)
        {
            Console.Write("카드를 더 뽑으시겠습니까? (y/n): ");
            string input = Console.ReadLine();

            if (input.ToLower() == "y") // 입력되는 y가 대문자는 소문자든 ToLower()로 전부 소문자로 반환
            {
                Card drawnCard = player.DrawCardFromDeck(deck);
                Console.WriteLine($"'{drawnCard}'() 뽑았습니다. 현재 총합은 
                {player.Hand.GetTotalValue()}점입니다.");
            }
            else
            {
                break;
            }
        }
        // 딜러의 차례, 총합이 17점이 넘을 때까지 계속해서 카드를 뽑는다
        Console.WriteLine("딜러의 차례입니다.");
        dealer.KeepDrawingCards(deck);
        Console.WriteLine($"딜러의 총합은 {dealer.Hand.GetTotalValue()}점입니다.");

        // 승자 판정

        if (player.Hand.GetTotalValue() > 21)
        {
            Console.WriteLine("플레이어의 카드 합이 21점을 초과했습니다. 딜러의 승리입니다.");
        }
        else if (dealer.Hand.GetTotalValue() > 21)
        {
            Console.WriteLine("딜러의 카드 합이 21점을 초과했습니다. 플레이어의 승리입니다.");
        }
        else if (player.Hand.GetTotalValue() > dealer.Hand.GetTotalValue())
        {
            Console.WriteLine("플레이어의 카드 합이 더 높습니다. 플레이어의 승리입니다.");
        }
        else
        {
            Console.WriteLine("딜러의 카드 합이 더 높거나 같습니다. 딜러의 승리입니다.");
        }
    }
}

전체 코드

namespace _3_Week_Assignment_2
{
    internal class Program
    {
        static void Main(string[] args)
        {
            BlackJack game = new BlackJack();
            game.PlayGame();
        }
    }
    public enum Suit { Hearts, Diamonds, Clubs, Spades } // 카드의 문양
    public enum Rank { Two = 2, Three, Four, Five, Six, Seven, Eight, Nine, Ten,
    Jack, Queen, King, Ace } // 카드의 숫자 정보

    public class Card
    {        
        public Suit Suit { get; private set; }        
        public Rank Rank { get; private set; }

        public Card(Suit s, Rank r)
        {
            Suit = s;
            Rank = r;
        }

        public int GetValue() // 카드에 숫자 정보를 입혀서 반환한다.
        {
            if ((int)Rank <= 10) // Two ~ Ten 까진 각자 순서에 맞게
            {
                return (int)Rank;
            }
            else if ((int)Rank <= 13) // Jack ~ King 는 10점
            {
                return 10;
            }
            else
            {
                return 11; // Ace는 1점, 11점인데 우선 기본은 11점 반환
            }
        }

        public override string ToString() // 카드의 정보를 문자열로 변환
        {
            return $"{Rank} of {Suit}";
        }
    }

    public class  Deck
    {
        // 카드가 들어있는 덱에 대한 정보
        private List<Card> cards;
        
        public Deck()
        {
            cards = new List<Card>();

            foreach (Suit s in Enum.GetValues(typeof(Suit)))
            {
                foreach (Rank r in Enum.GetValues(typeof(Rank)))
                {
                    cards.Add(new Card(s, r));
                }
            }
            Shuffle();
        }        
        public void Shuffle()
        {
            Random rand = new Random();

            for (int i = 0; i < cards.Count; i++)
            {
                int j = rand.Next(i, cards.Count);
                Card temp = cards[i];
                cards[i] = cards[j];
                cards[j] = temp;
            }
        }

        public Card DrawCard()
        {
            Card card = cards[0];
            cards.RemoveAt(0);
            return card;
        }
    }

    

    public class Hand
    {
        // 손에 들어간 카드에 대한 정보
        private List<Card> cards;

        public Hand()
        {
            cards = new List<Card>();
        }
        public void AddCard(Card card)
        {
            cards.Add(card);
        }
        public int GetTotalValue()
        {
            int total = 0;
            int aceCount = 0;

            foreach (Card card in cards)
            {
                if (card.Rank == Rank.Ace)
                {
                    aceCount++;
                }
                total += card.GetValue();
            }

            // 에이스가 있고 총점이 21점을 넘을 때, 에이스를 1점으로 취급
            while (total > 21 && aceCount > 0)
            {
                total -= 10;
                aceCount--;
            }

            return total;
        }
    }

    public class Player
    {
        // 플레이어에 대한 정보
        public Hand Hand { get; private set; }

        public Player()
        {
            Hand = new Hand();
        }

        // 카드를 뽑는 메소드
        public Card DrawCardFromDeck(Deck deck)
        {
            Card drawnCard = deck.DrawCard();
            Hand.AddCard(drawnCard);
            return drawnCard;
        }
    }

    public class  Dealer : Player // 기본적으로 플레이어 클래스의 정보와 같고 추가 정보가 있다
    {
        // 딜러에 대한 정보
        // 17점까지 카드를 계속 뽑게 하자
        public void KeepDrawingCards(Deck deck)
        {
            while (Hand.GetTotalValue() < 17)
            {
                Card drawnCard = DrawCardFromDeck(deck);
                Console.WriteLine($"딜러는 '{drawnCard}'() 뽑았습니다. 
                현재 총합은 {Hand.GetTotalValue()}점입니다.");
            }
        }
    }

    public class BlackJack
    {
        // 블랙잭 게임의 정보
        // 손에는 두 장의 카드가 있고 합이 21점에 가까우면 승리
        // 21점을 넘어가면 패배
        
        private Player player;
        private Dealer dealer;
        private Deck deck;        

        public void PlayGame()
        {            
            deck = new Deck();
            player = new Player();
            dealer = new Dealer();

            Console.WriteLine("게임을 시작합니다!\n");

            for (int i = 0; i < 2; i++) // 플레이어가 뽑은 카드를 보기 위해서 출력
            {
                Console.WriteLine(player.DrawCardFromDeck(deck));
            }

            Console.WriteLine($"플레이어의 초기 카드 합: {player.Hand.GetTotalValue()}\n");            

            Console.WriteLine($"딜러의 오픈 카드: {dealer.DrawCardFromDeck(deck)}\n"); 
            // 실제 블랙잭의 룰처럼 딜러의 카드 한 장만을 오픈
            dealer.DrawCardFromDeck(deck);


            // 플레이어의 차례, 21점이 넘지 않는다면 계속해서 카드를 뽑을 수 있다
            while (player.Hand.GetTotalValue() < 21)
            {
                Console.Write("카드를 더 뽑으시겠습니까? (y/n): ");
                string input = Console.ReadLine();

                if (input.ToLower() == "y")
                // 입력되는 y가 대문자는 소문자든 ToLower()로 전부 소문자로 반환
                {
                    Card drawnCard = player.DrawCardFromDeck(deck);
                    Console.WriteLine($"'{drawnCard}'() 뽑았습니다. 
                    현재 총합은 {player.Hand.GetTotalValue()}점입니다.");
                }
                else
                {
                    break;
                }
            }
            // 딜러의 차례, 총합이 17점이 넘을 때까지 계속해서 카드를 뽑는다
            Console.WriteLine("딜러의 차례입니다.");
            dealer.KeepDrawingCards(deck);
            Console.WriteLine($"딜러의 총합은 {dealer.Hand.GetTotalValue()}점입니다.");

            // 승자 판정

            if (player.Hand.GetTotalValue() > 21)
            {
                Console.WriteLine("플레이어의 카드 합이 21점을 초과했습니다. 딜러의 승리입니다.");
            }
            else if (dealer.Hand.GetTotalValue() > 21)
            {
                Console.WriteLine("딜러의 카드 합이 21점을 초과했습니다. 플레이어의 승리입니다.");
            }
            else if (player.Hand.GetTotalValue() > dealer.Hand.GetTotalValue())
            {
                Console.WriteLine("플레이어의 카드 합이 더 높습니다. 플레이어의 승리입니다.");
            }
            else
            {
                Console.WriteLine("딜러의 카드 합이 더 높거나 같습니다. 딜러의 승리입니다.");
            }
        }
    }
}

🤸🏻‍♀️Feedback

해설 코드는 시작 시 플레이어와 딜러 각자의 카드 합계만 보여주고 플레이어가 카드를 더 뽑을지 선택하는 방식이었다. 하지만 에이스의 존재가 카드 뽑기에 큰 영향을 끼치고, 딜러의 카드 한 장을 봐야 판단할 수 있는 문제기 때문에 입맛에 맞게 코드를 수정하였다.

0개의 댓글