C#으로 블랙잭 게임 만들기

2025. 10. 13. 20:46개발

현재 공부시간: 12시간 채우ㅓ

 

 

 

 

 

더보기
namespace BlackJackGame;

    class Card
    {
        public string Pattern; //카드 무늬 ♠, ♥, ♦, ♣
        public string Rank; //숫자, 등급 2~10, J, Q, K, A

        public Card(string pattern, string rank)
        {
            Pattern = pattern;
            Rank = rank; //필드 초기화. 카드객체를 유효하게 만들어줌
        }

        public int GetValue() //카드 점수 반환 (A는 11, J/Q/K는 10, 나머지는 숫자)
        {
            if (int.TryParse(Rank, out int value))
                return value;
            else if (Rank == "A")
                return 11;
            else 
                return 10;
        }
    }

    class Deck 
    {
        private List<Card> cards = new List<Card>();
        private Random random = new Random();

        public Deck()
        {
            string[] Pattern = {"♠", "♥", "♦", "♣" };
            string[] Rank = {"J", "Q", "K", "A","2", "3", "4", "5", "6", "7", "8", "9","10"};

            foreach (string pattern in Pattern)
            {
                foreach (string rank in Rank)
                {
                    cards.Add(new Card(pattern, rank));
                }
            }
        }

        public void Shuffle()  //게임 시작시 카드 더미(덱)을 섞음. 
        {
            for (int i=0; i<cards.Count; i++)
            {
                int j = random.Next(cards.Count);
                (cards[j], cards[i]) = (cards[j], cards[i]); //구조분해로 스왑
            }
        }
        
        public Card Draw()
        {
            Card drawnCard = cards[0];
            cards.RemoveAt(0);
            return drawnCard;
        }
    }

    class Hand //플레이어나 딜러의 손에 어떤 카드가 들어가느냐
    {
        public List<Card> Cards = new List<Card>();

        public void AddCard(Card card)
        {
            Cards.Add(card);
        }

        public int GetScore()
        {
            int score = 0;
            int aceCount = 0;
            
            foreach (Card card in Cards)
            {
                if (card.Rank == "J"|| card.Rank == "Q"|| card.Rank == "K")
                {
                    score += 10;
                }
                else if (card.Rank == "A")
                {
                    score += 11;
                    aceCount++;
                }
                else 
                {
                    score += int.Parse(card.Rank);
                }
            }
            
            while (score > 21 && aceCount > 0)
            {
                score -= 10;
                aceCount--;
            }
            return score;
        }
        
        //현재 손패를 출력하기
        public void ShowHand(string name)
        {
            Console.WriteLine($"{name}의 현재 손패:");

            foreach (Card card in Cards)
            {
                Console.Write(card.Pattern + card.Rank);
            }
            Console.WriteLine($"->점수:{GetScore()}");
        }
    }


    class GameManager //게임 한판을 순서대로 호출하면 됨 + 게임진행제어
    {
        private Deck deck;
        private Hand player;
        private Hand dealer;

        public void StartGame()
        {
            
            Console.WriteLine("----블랙잭 게임 시작----");
            Console.WriteLine("플레이어는 'hit'를 입력하면 카드를 한 장 더 받고, 'stand'를 입력하면 턴을 종료합니다.");
            Console.WriteLine("게임이 시작됩니다 \n");
            //덱(카드 더미)초기화,
            deck = new Deck();
            deck.Shuffle();
            
            //카드 나누고,
            player = new Hand();
            dealer = new Hand();
            
            //초기 카드 분배(각각 2장)
            player.AddCard(deck.Draw());
            dealer.AddCard(deck.Draw());
            player.AddCard(deck.Draw());
            dealer.AddCard(deck.Draw());
            
            //게임 루프 실행!
            PlayerTurn();
            DealerTurn();
            
            ShowResult();
        }

        private void PlayerTurn()
        {
            while (true)
            {
                Console.WriteLine("hit or stand?");
                string choice = Console.ReadLine();
                
                if (choice == "hit")
                {
                    player.AddCard(deck.Draw());
                    player.ShowHand("플레이어");

                    if (player.GetScore() > 21)
                    {
                        Console.WriteLine("Burst! 점수가 초과되었습니다."); 
                        break;
                    }
                }
                else if (choice == "stand")
                {
                    break;
                }
            }
        }

        private void DealerTurn()
        {
            //딜러 점수가 17보다 작으면 자동으로 카드 받기
            while (dealer.GetScore() <17)
            {
                dealer.AddCard(deck.Draw());
                dealer.ShowHand("딜러");
            }
        }

        private void ShowResult()
        {
            //플레이어 점수 계산
            int playerScore = player.GetScore();
            //딜러 점수 계산
            int dealerScore = dealer.GetScore();
            
            //burst 체크 (21초과 -> 패배)
            if (playerScore > 21)
            {
                Console.WriteLine("Player is Burst!");
            }
            else if (dealerScore > 21)
            {
                Console.WriteLine("Dealer is Burst!");
            }
            else
            {
                if (playerScore > dealerScore)
                    Console.WriteLine("Player Wins!");
                else if (playerScore < dealerScore)
                    Console.WriteLine("Dealer Wins!");
                else
                    Console.WriteLine("Draw!");
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            GameManager game =  new GameManager();
            game.StartGame();
        }
    }

 

  전체 코드 

 

 

 

 

 

스네이크 게임보다는 구현이 조금 더 쉬웠다. 

규칙이 명확해서 그런 듯! 응용해야 할 것도 그리 많지 않고... 스네이크 게임보다는 확실히 술술 풀리고 머리도 덜 아팠음

 

 

블랙잭 기본 규칙

  1. 목표:
    카드의 합이 21에 가장 가깝게 만드는 사람이 승리.
  2. 카드 규칙:
    • 숫자 2~10은 그대로 점수
    • J, Q, K는 10점
    • A(에이스)는 1 또는 11점 중 유리한 쪽으로 계산
  3. 진행 순서:
    1. 플레이어와 딜러에게 각각 2장씩 나눠줌
    2. 플레이어는 Hit(한 장 더) 또는 Stand(그만) 선택
    3. 플레이어가 21을 넘으면 → 버스트(패배)
    4. 딜러는 17점 이상이 될 때까지 카드를 자동으로 받음
    5. 둘 다 끝나면 점수 비교 → 21에 더 가까운 쪽 승리

 

 

 

게임 흐름 : 덱 생성 → 카드 섞기 → 플레이어/딜러 카드 나누기 → 플레이어 턴 → 딜러 턴 → 승패 판정

 

 

 

 

클래스 구조와 역할:

클래스 역할
Card 한 장의 카드 정보 (무늬, 숫자, 점수 계산)
Deck 52장 카드 묶음, 섞기·뽑기 담당
Hand 플레이어나 딜러의 카드 보유 & 점수 계산
GameManager 게임 한 판을 순서대로 호출함 / 게임 진행 제어

 

 

 

 

 

 

전체 구조 틀

using System;
using System.Collections.Generic;
using System.Linq;

namespace BlackjackGame
{
    // 카드 한 장
    class Card
    {
        public string Suit;  // 무늬: ♠, ♥, ♦, ♣
        public string Rank;  // 숫자: 2~10, J, Q, K, A

        // 생성자
        public Card(string suit, string rank)
        {
            // TODO: 필드 초기화
        }

        // 카드의 점수 반환 (A는 11, J/Q/K는 10, 나머지는 숫자)
        public int GetValue()
        {
            // TODO: 점수 계산 로직 작성
            return 0;
        }
    }

    // 카드 더미 (덱)
    class Deck
    {
        private List<Card> cards = new List<Card>();
        private Random rand = new Random();

        public Deck()
        {
            // TODO: 52장 카드 생성 (4무늬 × 13숫자)
        }

        // 카드 섞기
        public void Shuffle()
        {
            // TODO: 리스트 섞기 (Random 이용)
        }

        // 카드 한 장 뽑기
        public Card Draw()
        {
            // TODO: 한 장 반환 후 리스트에서 제거
            return null;
        }
    }

    // 손패 (플레이어나 딜러)
    class Hand
    {
        public List<Card> Cards = new List<Card>();

        // 카드 추가
        public void AddCard(Card card)
        {
            // TODO: 손패에 카드 추가
        }

        // 점수 계산
        public int GetScore()
        {
            // TODO: 카드 점수 합 + A 처리 로직
            return 0;
        }

        // 현재 손패 출력
        public void ShowHand(string name)
        {
            // TODO: 콘솔에 카드와 점수 출력
        }
    }

    // 메인 게임 흐름
    class GameManager
    {
        private Deck deck;
        private Hand player;
        private Hand dealer;

        public void StartGame()
        {
            // TODO: 덱/손패 초기화, 카드 나누기, 게임 루프 실행
        }

        private void PlayerTurn()
        {
            // TODO: Hit / Stand 선택, 점수 체크
        }

        private void DealerTurn()
        {
            // TODO: 딜러 점수 < 17 → 자동으로 카드 받기
        }

        private void ShowResult()
        {
            // TODO: 최종 점수 비교 후 승패 출력
        }
    }

    //실행 부분
    class Program
    {
        static void Main()
        {
            GameManager game = new GameManager();
            game.StartGame();
        }
    }
}

 

 

 

 

 

 


트러블 슈팅

 

 

 

 

 

1. 블랙잭 게임 필드 초기화 어떻게 해

public Card(string suit, string rank)
{
    Suit suit = new Suit();   
    Rank rank = new Rank();   
}

 

 

문제점:

 -새로운 지역 변수 Suit을 만듬 x

- 필드가 아니라 지역변수 초기화를 함 x

 

 

생성자 매개변수 이름과 필드 이름 대소문자 구분이 비슷해서 헷갈렸다.

C#에서는 이렇게 하면 필드에 값이 들어가는게 아니라 새로운 지역변수가 생겨버린다.

 

 

이 코드에서 문제는 왼쪽 Suit,Rank가 클래스의 필드가 아니라는 점이다!

왜냐면 이 구문은 새로운 지역 변수를 선언하는 문법이기 때문이다.

 

Suit suit = new Suit();

C#에서 **“타입이 Suit인 지역 변수 suit를 새로 만든다”**라는 뜻.

 

 

 

 

 

그렇다면 왜 필드를 초기화해줘야할까? 

 

필드 초기화는 "객체를 진짜로 유효하게 만드는 단계"이다.

위에 필드에서 내가 카드 객체를 만들었다면,

public string Pattern; //카드 무늬 ♠, ♥, ♦, ♣
public string Rank; //숫자, 등급 2~10, J, Q, K, A

 

밑은 필드 초기화를 통해 카드 객체를 유효하게 만듬!

Pattern = pattern;
Rank = rank; //필드 초기화. 카드객체를 유효하게 만들어줌

 

 

 

 

 

 

 

 

트러블 슈팅 2

 

 

연산자 기호 헷갈려..

또는|| 과 그리고&&가 제일 헷갈려 ..

언제 어느 상황에서 구분해서 써야 하는지도 헷갈린다..

 

 

 

문제점)

else if (Rank == "J" && "Q" && "K") return 10;

 

=>x 이건 문법적으로 틀렸다.. Q와 K는 그냥 문자열이라서 && 뒤에 쓰면 논리 연산이 안됨.

컴파일 오류남.

 

 

시도한 점)

 

😀(ME): 그럼 보간으로 바꿔도 틀려?

$"{Rank}" == "J" && "Q" && "K"

 

👾(GPT): 어 틀려~

 

  • $"{Rank}"는 Rank 값 문자열 그대로 가져오는 거고
  • 여전히 "Q"와 "K"는 그냥 문자열 → 논리 연산 불가
  • 보간(interpolation)은 문자열 합치거나 출력할 때 편리한 것이지, 여러 값 비교를 한 번에 처리해주진 않음 !!

내가 여기서 원하는 것은 J 또는 Q또는 K카드일 때 10을 반환해줘

이기 때문에, 또는 ||을 써야함. &&는 그리고임!

 

else if (Rank == "J" || Rank == "Q" || Rank == "K")
return 10;

 

😀: 아 ㅇㅋㅇㅋ

 

 

 

알게 된 점:

 

보간은 문자열을 합치거나 출력할 때 편리한 것이지, 여러 값의 비교를 한 번에 처리해주진 않는다.

또는 ||과 그리고 &&를 구분하자... 또는|| 그리고 && 또는|| 그리고 && 또는|| 그리고 &&

 

 

 

 

 

 

트러블 슈팅 3)

 

 

Q. private 필드여도 생성자에서 접근이 가능해...?

 

class Deck //카드 더미 (덱)
    {
        private List<Card> cards = new List<Card>();
        private Random random = new Random();

        public Deck()
        {
            
            // 52장의 카드 생성 (4무늬 x 13숫자)
        }

 

여기서 public Deck에 랜덤한 카드 52장이 섞여야 하는데 필드가 private이어도 상관 없어?

 

 

 

 

A. 엉 상관 없음. private필드여도 생성자에서 접근 가능함!

 

 

private 필드여도 생성자에서 접근 가능

  • private List<Card> cards → Deck 클래스 내부에서만 접근 가능
  • public Deck() 생성자는 Deck 클래스 내부이므로 자유롭게 사용 가능
  • 따라서 cards.Add(...) 같은 코드는 문제 없이 가능

즉, private라고 해서 같은 클래스 내부에서 못 쓰는 게 아님.

 

 

 

 

 

알게 된 점: 필드가 private이어도 같은 클래스 내부에서 사용할 수 있다...다만 외부 클래스에서는 접근 못함!

 

 

 

 

 

 

 

 

트러블슈팅4)

 

 

cards.Add(new Card(pattern, rank)); 이렇게 쓰면 4무늬 곱하기 13숫자가 나와?

ChatGPT의 말:

맞아 😄 네가 쓴 그대로라면 4무늬 × 13랭크 = 52장 카드가 나와.

예를 들어 이렇게 하면:

string[] Pattern = { "♠", "♥", "♦", "♣" };
string[] Rank = { "J", "Q", "K", "A","2","3","4","5","6","7","8","9","10" };

foreach (string pattern in Pattern)
{
    foreach (string rank in Rank)
    {
        cards.Add(new Card(pattern, rank));
    }
}

  • Pattern 루프 4번
  • Rank 루프 13번
  • 총 반복 횟수 = 4 × 13 = 52
  • 따라서 cards 리스트에 52장의 카드가 들어감

즉, cards.Add(new Card(pattern, rank)); 한 줄로 4무늬 × 13숫자 카드 완성 가능 😎


원하면 내가 카드 섞기(Shuffle)까지 이어서 그림으로 보여줄 수도 있어.

나의 말:

cards.Add(new Card(pattern, rank)); 이게 무슨 뜻이야 코드 설명 좀

ChatGPT의 말:

좋아 😄 차근차근 설명할게.


코드

cards.Add(new Card(pattern, rank));


1️⃣ new Card(pattern, rank)

  • Card 클래스의 새 객체를 만든다는 뜻
  • 생성자에 pattern과 rank를 전달
  • 예를 들어:
pattern = "♠"
rank = "A"

  • 그러면 스페이드 A 카드 객체가 만들어짐

2️⃣ cards.Add(...)

  • cards는 List<Card>
  • Add() 메서드는 리스트에 새 요소를 추가
  • 즉, 새로 만든 Card 객체를 cards 리스트에 넣는 것

3️⃣ 전체 의미

cards.Add(new Card(pattern, rank));

  • “pattern과 rank로 카드 객체를 만들고, 그 카드를 덱(cards) 리스트에 추가해라”
  • foreach 루프 안에서 이걸 반복하면 모든 무늬 × 모든 숫자 카드가 리스트에 쌓임
  • 결과적으로 52장의 카드 완성

💡 그림으로 생각하면

Pattern: ♠, ♥, ♦, ♣
Rank: 2,3,...,K,A

foreach pattern
    foreach rank
        cards.Add(new Card(pattern, rank))

→ cards 리스트: [♠2, ♠3, ..., ♣K, ♣A] 총 52장


원하면 내가 이 52장 카드 섞고 Draw해서 플레이어/딜러에게 나눠주는 흐름까지 이어서 보여줄 수도 있어.

나의 말:

1️⃣ new Card(pattern, rank)

Card 클래스의 새 객체를 만든다는 뜻

생성자에 pattern과 rank를 전달

예를 들어:

pattern = "♠" rank = "A"

그러면 스페이드 A 카드 객체가 만들어짐 이거 그럼 나중에 입력 받아야해?

ChatGPT의 말:

아니 😄 지금 구조에서는 나중에 입력 받을 필요 없어.


이유

  • new Card(pattern, rank) → 이미 코드 안에서 패턴과 랭크 배열을 반복문으로 넣어줌
  • 예시:
string[] Pattern = { "♠", "♥", "♦", "♣" };
string[] Rank = { "2", "3", ..., "K", "A" };

foreach (string pattern in Pattern)
{
    foreach (string rank in Rank)
    {
        cards.Add(new Card(pattern, rank));
    }
}

  • 여기서 모든 패턴 × 모든 랭크가 자동으로 Card 객체로 생성됨
  • 즉, 스페이드 A도, 하트 2도, 다 리스트에 들어감

🔹 결론

  • 사용자 입력 필요 없음
  • Deck 생성자에서 자동으로 52장 카드 만들어서 cards에 저장됨
  • 나중에 플레이어가 카드를 뽑을 때는 **Deck에서 Draw()**만 호출하면 됨

원하면 내가 Deck에서 Draw()로 카드 뽑고 플레이어/딜러에 나눠주는 흐름까지 그림으로 보여줄 수도 있어.

나의 말:

아 ㅇㅋ 나 이해함

ChatGPT의 말:

좋아 😄 완전히 이해했구나!

  • new Card(pattern, rank) → 자동으로 카드 객체 생성
  • cards.Add(...) → 리스트에 쌓아서 52장 덱 완성
  • 사용자 입력 따로 받을 필요 없음 → 나중에 Draw()로 카드만 뽑으면 됨

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

잡담: 연휴 끝나고 오랜만에 공부하니까 신체리듬이 와장창 깨져버림....분명 놀기만 한 건 아니었던 것 같은데...ㅎㅎㅎ

그래서 오늘은 그냥 신체리듬 되돌리는 날이라고 맘 먹고 좀 내려놓고 공부했다....