1. 게임 기획
- 게임 목표 정의 (21점을 넘지 않으면서 딜러보다 높은 점수 획득)
- 플레이어 수 (1인 vs 딜러)
- 승패 조건 정의
- 카드 규칙 요약 (A=1 또는 11, J/Q/K=10, 숫자카드=해당 숫자)
2. 기본 구조 설계
- 클래스 설계
- Card (문양, 숫자)
- Deck (카드 덱, 섞기, 카드 뽑기)
- Player (카드 리스트, 점수 계산)
- GameManager (게임 흐름 제어)
- Enum 설계
- Suit (Hearts, Spades, Clubs, Diamonds)
- Rank (Ace ~ King)
3. 게임 로직 구현
- 카드 덱 생성 및 셔플
- 플레이어/딜러에게 카드 2장씩 배분
- 점수 계산 로직 (A의 처리 포함)
- 플레이어 선택 (Hit / Stand)
- 딜러 동작 (점수 17 이상일 때까지 Hit)
- 승패 판정 및 출력
4. 입출력 처리
- 콘솔 출력 (카드 정보, 점수 등)
- 사용자 입력 처리 (Console.ReadLine())
5. 게임 반복 및 종료 처리
- 다시 시작 여부 묻기
- 플레이어, 덱 초기화 로직 추가
using System;
using System.Collections.Generic;
// 카드 무늬 정의
public enum Suit { Hearts, Diamonds, Clubs, Spades }
// 카드 숫자 정의 (J/Q/K는 10, Ace는 11로 처리)
public enum Rank
{
Two = 2, Three, Four, Five, Six, Seven, Eight, Nine, Ten,
Jack = 10, Queen = 10, King = 10, Ace = 11
}
// 개별 카드 클래스
public class Card
{
public Suit Suit { get; }
public Rank Rank { get; }
public Card(Suit suit, Rank rank)
{
Suit = suit;
Rank = rank;
}
// 카드의 점수 반환
public int GetValue() => (int)Rank;
public override string ToString()
{
return $"{Rank} of {Suit}";
}
}
// 카드 덱 클래스
public class Deck
{
private List<Card> cards = new List<Card>();
private Random rng = new Random();
public Deck()
{
// 모든 카드 조합 생성
foreach (Suit suit in Enum.GetValues(typeof(Suit)))
{
for (int i = 2; i <= 10; i++)
cards.Add(new Card(suit, (Rank)i));
cards.Add(new Card(suit, Rank.Jack));
cards.Add(new Card(suit, Rank.Queen));
cards.Add(new Card(suit, Rank.King));
cards.Add(new Card(suit, Rank.Ace));
}
Shuffle();
}
// 카드 섞기
public void Shuffle()
{
for (int i = 0; i < cards.Count; i++)
{
int j = rng.Next(i, cards.Count);
(cards[i], cards[j]) = (cards[j], cards[i]);
}
}
// 카드 한 장 뽑기
public Card DrawCard()
{
Card card = cards[0];
cards.RemoveAt(0);
return card;
}
}
// 플레이어 클래스 (플레이어 및 딜러 공통 사용)
public class Player
{
public string Name { get; }
public List<Card> Hand { get; private set; } = new List<Card>();
public Player(string name)
{
Name = name;
}
public void AddCard(Card card) => Hand.Add(card);
public void ClearHand() => Hand.Clear();
// 점수 계산 (Ace = 11 또는 1로 조절)
public int GetScore()
{
int score = 0;
int aceCount = 0;
foreach (var card in Hand)
{
int value = card.GetValue();
score += value;
if (card.Rank == Rank.Ace) aceCount++;
}
// 점수가 21 초과면 Ace를 1로 조정
while (score > 21 && aceCount > 0)
{
score -= 10;
aceCount--;
}
return score;
}
// 플레이어의 카드 보여주기
public void ShowHand(bool showAll = true)
{
Console.WriteLine($"\n[{Name}의 카드]");
for (int i = 0; i < Hand.Count; i++)
{
if (!showAll && i == 0)
Console.WriteLine("[첫 번째 카드는 비공개]");
else
Console.WriteLine(Hand[i]);
}
if (showAll)
Console.WriteLine($"현재 점수: {GetScore()}");
}
}
// 게임 전체를 관리하는 클래스
public class GameManager
{
private Deck deck;
private Player player;
private Player dealer;
// 게임 시작
public void StartGame()
{
deck = new Deck();
player = new Player("플레이어");
dealer = new Player("딜러");
Console.Clear();
Console.WriteLine("=== 블랙잭 게임을 시작합니다 ===");
DealInitialCards();
PlayerTurn();
if (player.GetScore() <= 21)
DealerTurn();
ShowResult();
}
// 처음 카드 두 장씩 배분
private void DealInitialCards()
{
player.ClearHand();
dealer.ClearHand();
player.AddCard(deck.DrawCard());
dealer.AddCard(deck.DrawCard());
player.AddCard(deck.DrawCard());
dealer.AddCard(deck.DrawCard());
player.ShowHand();
dealer.ShowHand(false); // 딜러는 한 장 숨김
}
// 플레이어 턴
private void PlayerTurn()
{
while (true)
{
Console.WriteLine("\n카드를 더 받으시겠습니까? (H: Hit, S: Stand)");
string input = Console.ReadLine()?.ToLower();
if (input == "h")
{
player.AddCard(deck.DrawCard());
player.ShowHand();
if (player.GetScore() > 21)
{
Console.WriteLine("버스트! 21점을 초과했습니다.");
return;
}
}
else if (input == "s")
{
return;
}
else
{
Console.WriteLine("잘못된 입력입니다. H 또는 S를 입력해주세요.");
}
}
}
// 딜러 턴 (17 이상일 때 멈춤)
private void DealerTurn()
{
Console.WriteLine("\n딜러의 턴 시작");
dealer.ShowHand();
while (dealer.GetScore() < 17)
{
Console.WriteLine("딜러가 카드를 받습니다...");
dealer.AddCard(deck.DrawCard());
dealer.ShowHand();
}
if (dealer.GetScore() > 21)
Console.WriteLine("딜러 버스트! 21점을 초과했습니다.");
else
Console.WriteLine("딜러가 멈췄습니다.");
}
// 최종 결과 출력
private void ShowResult()
{
int playerScore = player.GetScore();
int dealerScore = dealer.GetScore();
Console.WriteLine("\n=== 게임 결과 ===");
player.ShowHand();
dealer.ShowHand();
if (playerScore > 21)
Console.WriteLine("패배하셨습니다.");
else if (dealerScore > 21 || playerScore > dealerScore)
Console.WriteLine("승리하셨습니다!");
else if (playerScore < dealerScore)
Console.WriteLine("패배하셨습니다.");
else
Console.WriteLine("무승부입니다.");
}
}
// 프로그램 진입점
class Program
{
static void Main(string[] args)
{
while (true)
{
GameManager game = new GameManager();
game.StartGame();
Console.WriteLine("\n다시 시작하시겠습니까? (Y/N)");
string input = Console.ReadLine()?.ToLower();
if (input != "y")
break;
}
Console.WriteLine("게임을 종료합니다.");
}
}
와,,너무 어렵다.. 하나씩 모르는거 다시 정리해야겠다...
✅ 1. 자동 구현 프로퍼티는 뭘까?
public Suit Suit { get; }
public Rank Rank { get; }
처음에는 { get; }만 써 있는 게 뭔지 잘 몰랐다. 근데 이건 자동 구현 프로퍼티라고 해서
별도로 필드를 선언하지 않아도, C#이 자동으로 내부에 저장공간을 만들어주는 기능이었다!
즉, 아래 코드처럼
private Suit _suit;
public Suit Suit { get { return _suit; } }
간단하게 한 줄로 줄여서 표현한 게 바로:
public Suit Suit { get; }
이런 식으로 get만 있으면 외부에서 읽기만 가능하고, 값을 바꾸는 건 불가능하다는 뜻이다.
초기화는 생성자에서만 가능하다.
✅ 2. Rank는 enum인데 Ace가 들어오면 어떻게 11이 되는 걸까?
public enum Rank
{
Two = 2, Three, ..., Jack = 10, Queen = 10, King = 10, Ace = 11
}
처음엔 enum이라는 게 단순히 이름 붙은 그룹이라고 생각했는데,
이렇게 = 숫자를 붙여주면 실제로 숫자 값을 가질 수 있다는 걸 알게 됐다!
그리고 아래 코드를 보면:
public int GetValue() => (int)Rank;
여기서 Rank는 enum인데, (int)Rank라고 형변환을 해주면
정수 값으로 바뀌어서 리턴된다. 그래서 Rank.Ace가 들어오면 11이 되는 거다!
오... 이런 식으로 enum은 이름을 갖고 있지만 숫자로도 바꿀 수 있는건 몰랐다!
✅ 3. foreach (Suit suit in Enum.GetValues(typeof(Suit))) 이건 어려웠다...
처음엔 이 한 줄이 뭔 말인지 하나도 안 보였다.
근데 하나씩 뜯어보니까 이렇게 해석할 수 있었다:
- Enum.GetValues(typeof(Suit))
→ Suit 열거형에 정의된 값들을 전부 가져온다. 결과는 배열로 나온다.
→ 즉 [Hearts, Diamonds, Clubs, Spades] 이런 배열이 생김 - foreach (Suit suit in ...)
→ 그 배열을 하나씩 꺼내서 suit 변수에 담아 반복한다는 뜻
결국 이 코드는 모든 무늬(Suit)를 하나씩 꺼내서 루프를 돌리는 것이다!
foreach (Suit suit in Enum.GetValues(typeof(Suit)))
{
// suit는 Hearts → Diamonds → Clubs → Spades 순으로 반복
}
와... 처음엔 진짜 괴랄했는데 알고 나니까 엄청 유용한 문법이었다 😅
✅ 4. (cards[i], cards[j]) = (cards[j], cards[i]); 이건 뭐지?
처음에는 그냥 한 줄 스왑처럼 보였는데, 진짜로 되는 건가 싶었다.
찾아보니까 이건 튜플 스왑 문법이었고, C# 7.0 이상부터 지원되는 기능이었다!
즉, 아래 코드랑 똑같은 기능이다:
var temp = cards[i];
cards[i] = cards[j];
cards[j] = temp;
하지만 위 코드는 3줄이고, 아래는 단 1줄:
(cards[i], cards[j]) = (cards[j], cards[i]);
이게 된다니... 정말 깔끔하고 예쁘다!
근데 처음 봤을 땐 이게 뭐야? 싶을 정도로 생소했던 문법이었다!
사실 난 temp로 그냥 쓰는게 더 익숙해서..잘 기억해놨다가 써먹어야겠다!
✅ 5. 그 외에도 새롭게 이해한 문법들
Card card = cards[0];
cards.RemoveAt(0);
return card;
→ 이건 덱에서 맨 앞에 있는 카드 한 장을 꺼내고, 덱에서 제거해서 리턴하는 부분이다.
public List<Card> Hand { get; private set; } = new List<Card>();
→ 플레이어의 카드 리스트. 외부에서는 읽기만 가능하고 수정은 불가능.
foreach (var card in Hand)
{
int value = card.GetValue();
score += value;
if (card.Rank == Rank.Ace) aceCount++;
}
→ 카드 하나씩 꺼내서 점수 누적하고, Ace가 몇 개인지도 따로 세서
21 초과 시 점수 조정할 수 있도록 준비하는 로직이다.
✅
이번 블랙잭 게임을 만들면서 솔직히 다 만들지는 못했지만 모르는 문법도 꽤 있었다.
너무 어려웠지만 하나씩 차근차근 하면서 다시 또 배우면 된다!
항상 겸손하게 하나하나씩 천천히 배워가자! 개발자는 겸손할 수 밖에 없는 직업이기 때문이다.. 모르는게 너무많아..ㅠㅜ

'C#' 카테고리의 다른 글
C# - 예외처리(Exception Handling) (0) | 2025.07.09 |
---|---|
C# - 인터페이스와 열거형 (Interface, enum) (2) | 2025.07.09 |
C# - C#으로 간단한 아이템 매니저 구현하기 - List와 LINQ로 컬렉션 관리 연습 (0) | 2025.07.08 |
C# - Linq(Language Integrated Query) (0) | 2025.07.08 |
C# - 스택 프레임이란? (Stack Frame) vs 스택(Stack) (3) | 2025.07.08 |