내일배움캠프/강의 과제

C#문법 종합반 - 3주차 : 블랙잭 게임 만들기

서보훈 2024. 9. 23. 00:45

스네이크게임과 마찬가지로 제공된 코드를 사용하여 블랙잭 게임을 만들어야합니다.

제공된 코드는 다음과 같습니다.

더보기
using System;
using System.Collections.Generic;

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)
        {
            return (int)Rank;
        }
        else if ((int)Rank <= 13)
        {
            return 10;
        }
        else
        {
            return 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();
        }

        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
{
    // 코드를 여기에 작성하세요
}

// 블랙잭 게임을 구현하세요. 
public class Blackjack
{
    // 코드를 여기에 작성하세요
}

class Program
{
    static void Main(string[] args)
    {
        // 블랙잭 게임을 실행하세요
    }
}

카드 관련 코드는 모두 구현되어있는상태이기 때문에 실제로 구현해야하는 내용은 플레이어 클래스를 상속받은 딜러클래스의 추가 내용과 블랙잭 게임 플레이입니다.

 

블랙잭 게임은 카드의 합이 21을 넘지 않으면서 가장 근접한 플레이어가 승리하는게임입니다.

게임의 흐름은 딜러와 플레이어 모두 카드를 2장씩 뽑은 뒤 먼저 플레이어의 차례가 시작됩니다.

 

플레이어는 카드의 합이 21이 넘지 않을경우 카드를 추가로 뽑을 수 있습니다.

이는 카드의 합이 21이 넘지 않는한, 계속할 수 있습니다.

이때 플레이어가 가진 카드의 합이 21을 넘으면 플레이어는 패배합니다.

 

플레이어가 카드를 뽑지 않을경우 딜러의 차례로 넘어가며 딜러는 정해진 규칙에 따라 카드를 뽑습니다.

현재 과제에서는 딜러의 카드 합이 17점 미만일경우 카드를 뽑도록 정해져있습니다.

 

딜러의 카드합이 17을 넘어 턴이 종료되면 승패를 판정하게됩니다.

이때 플레이어의 카드 합이 21을 넘겼을경우 무조건 플레이어가 패배하게되고 플레이어가 21을 넘기지 않은 상태에서 딜러가 21을 넘길경우 플레이어는 승리하게 됩니다.

 

양측 모두 21을 넘기지 않으면 더 큰 숫자를 가진쪽이 승리하게 됩니다.

 

이 내용을 기반으로 코드를 짜도록 하겠습니다.

 


Dealer 클래스

먼저 딜러 클래스를 만들어주도록 하겠습니다.

현재 Dealer클래스의 부모 클래스인 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;
    }
}

덱으로부터 카드를 뽑는 함수를 가지고있으며 Hand 클래스를 객체로 가지고있는상태입니다.

딜러의 행동은 카드의 합이 17을 넘을때 까지 카드를 뽑는것 뿐이므로, 이 행동을 취할 함수 하나만 만들어주면 됩니다.

 

딜러 클래스의 구현입니다.

// 딜러 클래스를 작성하고, 딜러의 행동 로직을 구현하세요.
public class Dealer : Player
{
    // 코드를 여기에 작성하세요
    public void DrawAdditinalCard(Deck deck)
    {
        Console.WriteLine("딜러의 차례입니다.");
        while(Hand.GetTotalValue() < 17)
        {
            Card drawCard = deck.DrawCard();
            Hand.AddCard(drawCard);
            Console.WriteLine($"딜러가 {drawCard}를 뽑았습니다.");
            Console.WriteLine($"현재 딜러의 총점은 {Hand.GetTotalValue()}점 입니다.");
        }
        Console.WriteLine("딜러의 턴을 종료합니다.");
    }
}

핸드 클래스로부터 가진 카드의 총 합을 받고, 이 합이 17이 낮을경우 덱에서 카드를 계속 뽑도록 while문을 통해 만들어주었습니다.

 

해당 행동은 이미 카드의 합이 17을 넘긴경우 할 필요가 없기 때문에, do ~ while문을 사용하지 않습니다.

또한 이미 만들어진 덱에서 카드를 뽑아와야하기 때문에, 매개변수로 카드를 뽑아오게될 덱 클래스를 받아주었습니다.

 

카드와 관련된 모든 기능은 구현되어있기 때문에, 게임 플레이 전체를 다루는 Blackjack 클래스를 만들어주겠습니다.


Blackjack 클래스

클래스 전체 코드입니다.

더보기
// 블랙잭 게임을 구현하세요. 
public class Blackjack
{
    // 코드를 여기에 작성하세요
    //덱 객체 선언
    public Deck deck;

    //플레이어와 딜러 객체 선언
    public Player player;
    public Dealer dealer;

    public void PlayBlackjeck()
    {
        //게임을 시작할때마다, 새로운 객체 생성
        deck = new Deck();
        player = new Player();
        dealer = new Dealer();

        bool isWin = false;

        Console.WriteLine("블랙잭 게임을 시작합니다.");

        for(int i = 0; i < 2; i ++)
        {
            player.DrawCardFromDeck(deck);
            dealer.DrawCardFromDeck(deck);
        }

        Console.WriteLine($"현재 플레이어의 카드 합 : {player.Hand.GetTotalValue()}");
        Console.WriteLine($"현재 딜러의 카드 합 : {dealer.Hand.GetTotalValue()}");

        Console.WriteLine();

        Console.WriteLine("플레이어의 차례입니다.");
        while(player.Hand.GetTotalValue() < 21)
        {
            Console.WriteLine("카드를 더 뽑으시겠습니까? (y / n)");
            string input = Console.ReadLine();
            if(input.ToLower() == "y")
            {
                Card newCard = player.DrawCardFromDeck(deck);
                Console.WriteLine($"{newCard}를 뽑았습니다.");
                Console.WriteLine($"현재 플레이어의 점수는 {player.Hand.GetTotalValue()} 입니다.");
            }
            else if(input.ToLower() == "n")
            {
                Console.WriteLine("플레이어의 턴을 종료합니다.");
                Console.WriteLine($"플레이어의 최종 점수는 {player.Hand.GetTotalValue()} 입니다.");
                break;
            }
            else
            {
                Console.WriteLine("잘못된 입력입니다. y, n 중 하나를 입력해주시기 바랍니다.");
            }
        }

        dealer.DrawAdditinalCard(deck);
        Console.WriteLine($"딜러의 최종 점수는 {dealer.Hand.GetTotalValue()} 입니다.");
        
        if(player.Hand.GetTotalValue() > 21)
        {
            Console.WriteLine("플레이어의 점수가 21점을 넘었습니다.");
            isWin = false;
        }
        else if(dealer.Hand.GetTotalValue() > 21)
        {
            Console.WriteLine("딜러의 점수가 21점을 넘었습니다.");
            isWin = true;
        }
        else if(player.Hand.GetTotalValue() > dealer.Hand.GetTotalValue())
        {
            Console.WriteLine("플레이어의 점수가 딜러의 점수보다 높습니다.");
            isWin = true;
        }
        else
        {
            Console.WriteLine("딜러의 점수가 플레이어와 같거나 높습니다.");
            isWin = false;
        }

        Console.WriteLine();

        if(isWin)
        {
            Console.WriteLine("-----플레이어의 승리-----");
        }
        else
        {
            Console.WriteLine("------딜러의 승리-----");
        }
    }
}

순서대로 보도록 하겠습니다.

 

먼저 필드로 플레이어와 딜러, 덱의 객체를 저장할 변수를 만들어주었습니다.

public class Blackjack
{
    // 코드를 여기에 작성하세요
    //덱 객체 선언
    public Deck deck;

    //플레이어와 딜러 객체 선언
    public Player player;
    public Dealer dealer;
}

 

이후 함수로 블랙잭 게임의 모든 흐름을 당담하는 함수를 만들었습니다.

필드에 선언한 클래스의 객체를 해당 함수에서 생성해주고, 승리 판정을위한 bool 변수를 만들어주었습니다.

 public void PlayBlackjeck()
 {
     //게임을 시작할때마다, 새로운 객체 생성
     deck = new Deck();
     player = new Player();
     dealer = new Dealer();

     bool isWin = false;

     Console.WriteLine("블랙잭 게임을 시작합니다.");
}

 

게임 시작과 동시에 딜러와 플레이어는 카드를 2장씩 뽑습니다.

for(int i = 0; i < 2; i ++)
{
    player.DrawCardFromDeck(deck);
    dealer.DrawCardFromDeck(deck);
}

Console.WriteLine($"현재 플레이어의 카드 합 : {player.Hand.GetTotalValue()}");
Console.WriteLine($"현재 딜러의 카드 합 : {dealer.Hand.GetTotalValue()}");

Console.WriteLine();

카드를 뽑은뒤 플레이어의 턴을 시작합니다.

플레이어는 카드의 합이 21보다 작으면 계속 카드를 뽑을 수 있기때문에, while문을 사용하여 반복을 처리해줍니다.

 

블랙잭은 킹,퀸,잭으로 10으로, 에이스를 11으로 처리하기 때문에, 첫 패에 에이스와 그림패가 잡혀 21이 만들어지면 플레이어의 턴이 진행되지 않습니다.

따라서 do while문은 사용할 수 없습니다.

Console.WriteLine("플레이어의 차례입니다.");
while(player.Hand.GetTotalValue() < 21)
{
    Console.WriteLine("카드를 더 뽑으시겠습니까? (y / n)");
    string input = Console.ReadLine();
    if(input.ToLower() == "y")
    {
        Card newCard = player.DrawCardFromDeck(deck);
        Console.WriteLine($"{newCard}를 뽑았습니다.");
        Console.WriteLine($"현재 플레이어의 점수는 {player.Hand.GetTotalValue()} 입니다.");
    }
    else if(input.ToLower() == "n")
    {
        Console.WriteLine("플레이어의 턴을 종료합니다.");
        Console.WriteLine($"플레이어의 최종 점수는 {player.Hand.GetTotalValue()} 입니다.");
        break;
    }
    else
    {
        Console.WriteLine("잘못된 입력입니다. y, n 중 하나를 입력해주시기 바랍니다.");
    }
}

카드를 뽑을지 여부는 y,n을 입력하는것으로 처리하며 y를 입력하면 새로운 카드를 뽑고, n을 입력하면 차례를 종료합니다.

또한 카드를 뽑을때마다 뽑은 카드의 종류와 점수의 총합을 출력하여 플레이어에게 알립니다.

 

플레이어가 n을 입력하여 카드를 받지 않으면 딜러의 턴을 시작합니다.

dealer.DrawAdditinalCard(deck);
Console.WriteLine($"딜러의 최종 점수는 {dealer.Hand.GetTotalValue()} 입니다.");

딜러의 행동은 Dealer 클래스의 DrawAdditinalCard 에 모두 구현해두었기 때문에, 해당 함수를 호출하여 처리합니다.

또한 딜러의 턴이 끝나면 딜러가 가진 카드의 점수를 출력합니다.

 

승패를 판정하는 코드입니다.

if(player.Hand.GetTotalValue() > 21)
{
    Console.WriteLine("플레이어의 점수가 21점을 넘었습니다.");
    isWin = false;
}
else if(dealer.Hand.GetTotalValue() > 21)
{
    Console.WriteLine("딜러의 점수가 21점을 넘었습니다.");
    isWin = true;
}
else if(player.Hand.GetTotalValue() > dealer.Hand.GetTotalValue())
{
    Console.WriteLine("플레이어의 점수가 딜러의 점수보다 높습니다.");
    isWin = true;
}
else
{
    Console.WriteLine("딜러의 점수가 플레이어와 같거나 높습니다.");
    isWin = false;
}

먼저, 플레이어의 점수가 21을 넘을경우, 어떤경우에도 플레이어가 패배하기 때문에 if문의 첫번째 행동으로 만들어줍니다.

 

이후 두번째 처리로 딜러의 카드가 21일 넘겼는지 체크하고, 이후 딜러와 플레이어의 숫자를 비교해서 승패를 처리합니다.

 

상위내용이 만족하면 즉시 if문을 끝내야함으로 else if 문으로 모든 처리를 묶어줍니다.

 

마지막으로 승패의 결과를 알려줍니다.

Console.WriteLine();

if(isWin)
{
    Console.WriteLine("-----플레이어의 승리-----");
}
else
{
    Console.WriteLine("------딜러의 승리-----");
}

 


메인 함수

Blackjack 클래스에서 게임의 모든 흐름을 만들어주었지만, 해당함수를 호출해줄 위치가 필요합니다.

메인함수에서 Blackjack 클래스의 PlayBlackjeck을 호출하여, 게임을 시작합니다.

internal class Program
{
    static void Main(string[] args)
    {
        while (true)
        {
            Blackjack game = new Blackjack();
            game.PlayBlackjeck();

            Console.WriteLine("다시 플레이하시겠습니까? (y/n)");
            Console.WriteLine();
            string input = Console.ReadLine();
            if(input.ToLower() == "y")
            {
                Console.Clear();
            }
            else
            {
                Console.WriteLine("게임을 종료합니다.");
                break;
            }
        }
    }
}

객체를 새로 만들어줄때, 덱이 새로 생성되기때문에 while문의 시작에 Blackjack 객체를 만들고 함수를 호출합니다.

 

이후 게임이 끝나면 게임을 다시 플레이할지 고르고 y를 입력하면 반복문이 시작, 그 외의 입력을 받으면 게임을 종료하도록 합니다.

 

게임의 흐름입니다.

세부 내용을 제외한 주요 코드 내용이 풀이와 매우 유사하기 때문에, 풀이와의 비교는 넘어가도록 하겠습니다.