개발일지

[C# 문법 종합반] 3주차 과제 - 스네이크 게임, 블랙잭

Hwone 2023. 8. 17. 15:07

스네이크 게임 

코드

더보기

SnakeGame.cs

namespace TimspartaBasic
{
    internal class SnakeGame
    {
        public class Position // x,y 좌표를 표현하는 클래스
        {
            public int X { get; set; }
            public int Y { get; set; }
            public Position(int x, int y)
            {
                X = x;
                Y = y;
            }
        }

        public class Snake
        {
            List<Position> body;
            public Position direction { get; set; }

            public Snake(int x, int y)
            {
                body = new List<Position>
                {
                    new Position(x, y),
                    new Position(x-1,y),
                    new Position(x-2, y)
                };
                direction = new Position(1, 0);
            }

            public void Draw() //처음 뱀 그리기
            {
                for (int i = 0; i < body.Count; i++)
                {
                    Console.SetCursorPosition(body[i].X, body[i].Y);
                    if (i == 0) //머리
                        Console.Write("@");
                    else //꼬리
                        Console.Write("*");
                }
            }

            public void Move() //이동
            {
                Position newHead = new Position(body[0].X + direction.X, body[0].Y + direction.Y); // 이동할 위치 계산
                body.Insert(0, newHead); // 위치를 머리로 넣어주기 
                body.RemoveAt(body.Count - 1); // 꼬리 하나 줄이기
            }

            public void DrawTail() //꼬리추가
            {
                Position tail = body[body.Count - 1];
                body.Add(tail);
            }

            public bool IsGameOver(int maxWidth, int maxHeight) //게임 종료 조건
            {
                Position head = body[0];

                //벽 충돌
                if (head.X <= 0 || head.X >= maxWidth - 1 || head.Y <= 0 || head.Y >= maxHeight - 1)
                {
                    return true;
                }

                //자기 몸통 충돌
                for (int i = 1; i < body.Count; i++)
                {
                    if (body[i].X == head.X && body[i].Y == head.Y)
                    {
                        return true;
                    }
                }
                return false;
            }

            public bool EatFood(FoodCreator food) // 음식 먹기
            {
                foreach(var segment in body)
                {
                    if (segment.X == food.position.X && segment.Y == food.position.Y)
                    {
                        return true; 
                    }
                }
                return false;
            }
        }

        public class FoodCreator 
        {
            public Position position { get; set; }

            Random random;
            int maxWidth;
            int maxHeight;
            public FoodCreator(int maxWidth, int maxHeight)
            {
                this.maxWidth = maxWidth;
                this.maxHeight = maxHeight;
                random = new Random();
                Respawn();
            }

            public void Respawn() //재생성
            {
                position = new Position(random.Next(1, maxWidth - 1), random.Next(1, maxHeight - 1));
            }

            public void Draw() //그리기
            {
                Console.SetCursorPosition(position.X, position.Y);
                Console.Write("$");
            }
        }
      

        static void Main(string[] args)
        {
            Console.CursorVisible = false; //커서 숨김

            int width = 70;
            int height = 20;

            Snake snake = new Snake(width / 2, height / 2);
            FoodCreator food = new FoodCreator(width, height);

            while (true)
            {
                if (Console.KeyAvailable) //방향전환
                {
                    var Key = Console.ReadKey(true).Key;

                    switch (Key)
                    {
                        case ConsoleKey.UpArrow:
                            snake.direction = new Position(0, -1);
                            break;
                        case ConsoleKey.DownArrow:
                            snake.direction = new Position(0, 1);
                            break;
                        case ConsoleKey.LeftArrow:
                            snake.direction = new Position(-1, 0);
                            break;
                        case ConsoleKey.RightArrow:
                            snake.direction = new Position(1, 0);
                            break;
                    }
                }

                if (snake.EatFood(food)) //음식을 먹었다면 꼬리 생성후 음식 재생성
                {
                    snake.DrawTail();
                    food.Respawn();
                }
                else
                {
                    snake.Move(); 
                }

                if (snake.IsGameOver(width, height)) //게엠 종료
                {
                    Console.Clear();
                    Console.SetCursorPosition(width / 2 - 5, height / 2);
                    Console.WriteLine("Game Over");
                    break;
                }

                DrawWall(width + 1, height + 1);
                snake.Draw();
                food.Draw();
                Thread.Sleep(100);
            } 
        }
        static void DrawWall(int width, int height) // 벽그리기
        {
            Console.SetCursorPosition(0, 0);

            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    if (y == 0 || y == height - 1 || x == 0 || x == width - 1)
                    {
                        Console.Write("#");
                    }
                    else
                    {
                        Console.Write(" ");
                    }
                }
                Console.WriteLine();
            }
        }
    }
}

후기

강의 자료에서 참고용을 바탕으로 만들어보려고 하였으나 뭔가 더 어려운 느낌이 들어서 싹 갈아엎어서 새로 만들게 되었다 구글링과 ChatGPT의 도움을 받으며 구현하였다 

 

Console.Clear() 를 이용하여 화면을 계속 업데이트 하여 뱀이 움직이는 기능을 구현하였는데 여기서 화면 깜빡임이 심하게 느껴졌다 그래서 일단 벽을 그리는 메서드에는 Console.SetCursorPosition(0,0); 추가하여 화면을 전체적으로 지우고 다시 그리는 것이 아니라,  커서의 위치를 이동하고 문자를 덮어쓰는 방식으로 화면 갱신을 부드럽게 만들었다 벽의 움직임은 잡혔지만 음식이나 뱀은 잡히지 않았다 튜터님에게 물어보니 이건 어쩔수 없는 일이니 넘어가도 될거 같다고 하셨다 

뭔가 찝찝한 느낌이 들어 강의 자료의 해설지를 보며 Console.Clear()를 사용하지 않아도 움직이는 구현에 대해 알아보았더니 공백으로 지우고 직접 다시 그리는 흐름으로 되어있었다 

 

이렇게 내 마음대로 만들었을 때의 단점이 있지만 장점도 분명히 존재했다 구현 능력이 좀 더 상승한 것 같고 내가 넣고 싶은 기능을 더 추가할 수 있었다 

나는 강의자료와 다르게 머리와 꼬리의 모양을 다르게 하여 머리가 무엇인지 직관적으로 더 알 수 있게 하였다 

 

 

블랙잭

코드

더보기

BlackjackGame.cs

namespace TimspartaBasic
{
    internal class BlackjackGame
    {
        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) // K,Q,J 카드에 대한 처리 10으로 맞춤
                    return 10;
                else //Ace 카드에 대한 처리 (일단 11로 맞춤) 
                    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 //손패를 표현하는 클래스 
        {
            public List<Card> cards; //가진 카드 리스트

            public Hand()
            {
                cards = new List<Card>();
            }

            public void AddCard(Card card) //카드 추가
            {
                cards.Add(card); 
            }

            public int GetTotlaValue() //최종 점수
            {
                int total = 0;
                int aceCount = 0; 

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

                // 점수가 21을 초과하고 Ace 카드가 포함되어있다면 Ace 카드의 점수를 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 virtual void ShowHand()
            {
                Console.WriteLine($"===== player =====");
                foreach (var card in Hand.cards)
                {
                    Console.WriteLine(card.ToString());
                }
                Console.WriteLine("점수 : " + Hand.GetTotlaValue());
            }
        }


        public class Dealer : Player //딜러를 표현하는 클래스
        {
            public bool isFirstTurn { get; set; } // 딜러가 처음 카드를 받았는지 체크하는 변수
            public Dealer()
            {
                isFirstTurn = true;
            }
            
            public override void ShowHand() 
            {
                Console.WriteLine("===== 딜러 ====="); 
                
                if(isFirstTurn)  // 딜러는 처음 카드를 받을 때 두번째로 받은 카드를 보여주지 않는다
                {
                    Console.WriteLine(Hand.cards[0].ToString());
                    Console.WriteLine("???");
                    Console.WriteLine("점수: ??"); 
                }
                else // 딜러의 차례에서 카드 공개 
                {
                    foreach(var card in Hand.cards)
                    {
                        Console.WriteLine($"{card.ToString()}");
                    }
                    Console.WriteLine("점수: "+ Hand.GetTotlaValue());
                }
            }
        }

        public class Blackjack //블랙잭 게임로직 구현 
        {
            Deck deck;
            Player player;
            Dealer dealer;

            bool isPlayerBust = false; 
            bool isDealerBust = false;

            public Blackjack()
            {
                deck = new Deck();
                player = new Player();
                dealer = new Dealer();
            }
            public void ScreenChange() // 메인화면 전환 
            {
                while(true)
                {
                    ConsoleKeyInfo key = Console.ReadKey(true);
                    if (key.Key == ConsoleKey.Enter)
                    {
                        Console.Clear();
                        break; 
                    }
                    else
                    {
                        Console.WriteLine("잘못된 입력입니다");
                        Thread.Sleep(1000); 
                        Console.SetCursorPosition(0, Console.CursorTop - 1); // 이전에 출력한 라인의 처음으로 이동
                        Console.Write(new string(' ', Console.WindowWidth)); // 해당 라인을 공백으로 채움
                        Console.SetCursorPosition(0, Console.CursorTop); // 커서를 지금 있는 행의 맨 앞으로 이동 ( 라인을 공백으로 채워서 커서는 맨 뒤로 가 있다) 
                    }
                }
                
            }

            public void Run()
            {
                Console.WriteLine("==========블랙잭 게임을 시작합니다.==========");
                Console.WriteLine("처음엔 플레이어 -> 딜러 순으로 카드 두장을 받고 시작합니다 (단, 딜러의 두번째 카드는 보여주지 않습니다. ");
                Console.WriteLine("화면 넘김 : enter.");

                ScreenChange();

                for (int i = 0; i < 2; i++) // 카드를 2장씩 받는다 
                {
                    player.DrawCardFromDeck(deck);
                    dealer.DrawCardFromDeck(deck);
                }

                while(player.Hand.GetTotlaValue() <= 21)
                {
                    Console.Clear();

                    Console.WriteLine("플레이어의 차례");
                    player.ShowHand();
                    dealer.ShowHand();
                    Console.WriteLine("Hit ? Stay ? (Push H or S)");
                    char playerChoice = Console.ReadKey().KeyChar;
                    
                    if ((playerChoice == 'H' || playerChoice == 'h') && !isPlayerBust )
                    {
                        player.DrawCardFromDeck(deck);
                        Console.Clear();
                        player.ShowHand();
                        dealer.ShowHand();

                        if (player.Hand.GetTotlaValue() > 21)
                        {
                            Console.WriteLine("Bust!!");
                            isPlayerBust = true;
                        }
                    }
                    else if (playerChoice == 'S' || playerChoice == 's' )
                    {
                        break;
                    }
                    else
                    {
                        Console.WriteLine(); 
                        Console.WriteLine("잘못된 입력입니다");
                        Thread.Sleep(1000); 
                        continue; 

                    }
                }

                
                while ((dealer.Hand.GetTotlaValue() < 17 && dealer.Hand.GetTotlaValue() < 21) || dealer.isFirstTurn ==true)
                {
                    Console.Clear();
                    Console.WriteLine("딜러의 차례");
                    dealer.isFirstTurn = false;
                    dealer.DrawCardFromDeck(deck);
                    player.ShowHand();
                    dealer.ShowHand();
                    Thread.Sleep(1000);
                    if (dealer.Hand.GetTotlaValue() > 21)
                    {
                        isDealerBust = true;
                        Console.WriteLine("딜러 Bust!!");
                    }
                }

                if(isDealerBust) // 게임 종료 로직
                {
                    if (isPlayerBust)
                        Console.WriteLine("무승부!");
                    else
                        Console.WriteLine("플레이어 승!"); 
                }
                else 
                {
                    if(isPlayerBust)
                        Console.WriteLine("딜러 승!");
                    else
                    {
                        if (player.Hand.GetTotlaValue() > dealer.Hand.GetTotlaValue())
                            Console.WriteLine("플레이어 승!");
                        else if (player.Hand.GetTotlaValue() < dealer.Hand.GetTotlaValue())
                            Console.WriteLine("딜러 승!");
                        else
                            Console.WriteLine("무승부!"); 
                    }
                }
                
                Console.ReadKey();
            }
        }

        static void Main(string[] args)
        {
            //게임 실행 
            Blackjack blackjack = new Blackjack();
            blackjack.Run();
        }
    }
}

후기 

이 블랙잭게임은 개인적으로 진짜 재미있게 만들었던 것 같다 (밥 시간을 줄여서 얼른 만들고 싶었을 정도) 

 

강의자료의 참고 코드를 바탕으로 내 코드를 추가하여 어렵지 않게 만들 수 있었다

참고 코드를 간단히 읽어보고 필요한 코드를 추가하여 만들고 난 후 참고 코드로 돌아가 분석을 하고 왜 이렇게 작성되었는지 복습하였다 

 

첫 시작 화면에서 enter키를 누르면 화면을 넘겨 게임을 시작하는 기능을 구현했는데 다른 키를 눌러도 넘어가거나 잘못된 입력입니다" 가  무한루프 때문에 계속 출력되는 현상이 생기기도 하였다 그래서 Thred.Sleep()을 이용하여 잠시 1초동안 출력후 Console.CusorTop(현재 커서의 세로(행) 좌표를 나타냄)을 이용하여 맨 앞으로 이동하고 WindowWidth(콘솔창의 한 행의 최대 문자수를 나열)를 이용하여 공백으로 다 지워줬다

 

그리고 강의 자료에 나와있지는 않지만 원래 블랙잭의 룰을 따라서 처음 받은 딜러의 두번째 카드는 보여주지 않고 있다가 

플레이어 턴이 끝나면 보여주도록 추가하였다 

 

전체적인 소감 

개발을 하기 위해 뭐가 필요하고 어떻게 흘러갈지에 대해 머리가 너무 어지럽고 어려웠다

 구현 전에 전체적인 흐름을 생각하고 일단 뭐든 코드를 짜보는게 중요하고 많이 경험해 보면서 나만의 형식, 구조를 만들어보는게 좋다는 튜터님의 조언을 새겨들으며 다른 게임도 만들어 봐야할것 같다

블랙잭은 재밌었지만 스네이크는 너무 어려웠다...