20231107_기록_스네이크 게임

2023. 11. 7. 21:29IT/TIL

오늘 한 것들

 

코드 컨벤션 특강 OT 참가

C# 문법 종합반 3주차 수강

C# 문법 종합반 3주차 과제 - 1. 스네이크 게임

C# 문법 종합반 3주차 과제 - 2. 블랙잭 게임

 

오늘은 원래 C# 문법 종합반을 마무리하려고 하였으나,

3주차의 과제인 스네이크 게임과 블랙잭 게임이 생각보다 난적이라 진행하지 못했다.

 

각종 게임만들기 책에 단골로 등장하는 스네이크 게임은 그 기본적인 틀은 어렵지 않으나

C#에서 구현하려니 살짝 막막한 감이 있었다.

 

코딩하기 전에 구현할 내용과 구현할 순서를 정하고 구현을 시도했는데,

더보기

// 스네이크 게임
// 매 턴 한 칸씩 진행 방향으로 이동하기 -> 맨 앞의 칸에 새로 만들고, 맨 뒤 칸을 하나 지운다.
// 방향키를 입력하여 이동 제어 -> 방향키 받아오기 + 받아온 방향키로 이동하기
// 무작위로 음식 생성하기 + 음식 먹으면 길이 길어지고 점수 올라가기
// 자신의 몸이나 벽에 부딛히면 게임 종료

// Snake 클래스 만들기 -> 뱀의 상태, 이동, 음식 먹기, 자신의 몸에 부딛혔는지 확인
// FoodCreator 클래스 만들기 -> 맵의 크기 내에서 무작위 위치에 음식을 생성하기
// Main 함수에서 게임 제어하는 코드 만들기 -> 뱀의 이동, 음식 먹기, 게임 오버 조건 확인

// 구현 진행 방향
// 1. 맵 만들기
// 2. 뱀 만들기 -> 클래스화 시키기
// 3. 뱀의 이동 구현하기
// 4. 음식을 무작위 위치에 생성하기
// 5. 뱀이 음식을 먹는 매커니즘 작성하기 (먹기 + 길어지기 + 새로운 음식 생성 + 점수 상승, 속도 업)
// 6. 게임 종료 매커니즘 작성하기(벽에 부딛히기, 자신의 몸에 부딛히기)

// 1. 맵 만들기

// 2. 뱀 그리기
// 초기 몸 길이 5, 초기 위치 20, 10, 초기 방향 오른쪽(0)
// 오른쪽 - 0, 위쪽 - 1, 왼쪽 - 2, 아래쪽 - 3으로 direction 구현
// 처음에 그려진 상태에서 키 입력시 이동 시작
// Console.ReadKey()를 받아와서 해당 값으로 숫자 반영, 숫자 값에 따라서 방향 정하기

// ...

 

게임의 기본은 틀이라고 생각하기에 우선 맵을 만들었다.

이전과 다르게 맵을 만드는 과정에서 SetCursorPosition을 도입했는데,

Console.SetCursorPosition(x, y);

Console.Write("#");

의 방식으로 문자 "#"를 사용하는 방식으로 특정 위치에 원하는 문자를 입력할 수 있는 방법으로,

이 기술 없이는 이 게임을 만들기 어려울 것으로 판단되어 이 개념을 공부하고 응용할 수 있게 하였다.

 

이후에 뱀을 그리는 과정에서

필요한 함수들을 우선 하나씩 만드는 과정을 거쳤는데,

뱀의 초기 위치와 초기 상태를 그리고,

뱀을 그리는 로직을 만들고,

키보드의 입력을 받아 뱀의 이동 방향을 정하는 로직을 만들고,

해당 방향으로 뱀이 이동하고, 다시 뱀을 그리는 로직을 만들었다.

 

이후에 음식을 무작위 위치에 배치하고,

뱀이 이동하다 음식을 만나면 길이가 1만큼 길어지고,

새로운 음식을 무작위 위치에 배치하고,

점수를 올리고, 게임 속도를 올리는 과정을 거쳤다.

 

이후에 게임이 마무리될 수 있게

벽과 충돌하면 게임 오버가 되게, 자신의 몸과 충돌해도 게임 오버가 되게하여 작업을 마무리했다.

 

마지막으로 함수들로 만들었던 로직들을

Snake 클래스로 만들어서 그 아래로 넣을 수 있는 것들은 하위로 넣으면서 코드를 좀 더 깔끔하게 만들었다.

과정에서 클래스와 접근 제한자에서 문제가 발생하여 이를 고치는 과정에서 많은 두뇌회전을 느꼈다.

 

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

namespace homework_3_snake
{
    class Program
    {
    	// 맵 데이터
        public static int width = 80;
        public static int height = 20;

        // 외적 요소
        public static int gameSpeed = 100;
        public static int score = 0;

        static void drawMap()
        {
            Console.CursorVisible = false;

            // Draw the borders
            for (int i = 0; i < width; i++)
            {
                    Console.SetCursorPosition(i, 0);
                    Console.Write("#");
                Console.SetCursorPosition(i, height - 1);
                Console.Write("#");
            }

            for (int i = 0; i < height; i++)
            {
                Console.SetCursorPosition(0, i);
                Console.Write("#");
                Console.SetCursorPosition(width - 1, i);
                Console.Write("#");
            }
        }

        static void Main(string[] args)
        {
            drawMap();
            Snake snake = new Snake(20, 10);
            Console.WriteLine(score);

            while (true)
            {
                if (Console.KeyAvailable)
                {
                    ConsoleKeyInfo key = Console.ReadKey(true);
                    snake.ChangeDir(key);
                }

                snake.makeFood();
                snake.MoveSnake();
                snake.drawScore();
                snake.IsColl();
                System.Threading.Thread.Sleep(gameSpeed);
            }
        }
    }

    class Snake
    {
        // snake 데이터
        private List<int[]> snakeBody = new List<int[]>();
        private int snakeDir;
        private int xPos;
        private int yPos;

        // food 데이터
        private int foodCnt;
        private int foodX;
        private int foodY;

        // 외적 요소
        private int gameSpeed;
        private int score;
        private int width;
        private int height;

        public Snake(int x, int y)
        {
            snakeDir = 0;
            xPos = x;
            yPos = y;
            foodCnt = 0;
            gameSpeed = Program.gameSpeed;
            score = Program.score;
            width = Program.width;
            height = Program.height;
            InitializeSnake();
        }

        public void InitializeSnake()
        {
            int[] head = { xPos, yPos };
            snakeBody.Add(head);
            for (int i = 1; i <= 4; i++)
            {
                int[] bodyPart = { head[0] - i, head[1] };
                snakeBody.Add(bodyPart);
            }
            DrawSnake();
        }

        public void DrawSnake()
        {
            foreach (int[] bodyPart in snakeBody)
            {
                Console.SetCursorPosition(bodyPart[0], bodyPart[1]);
                Console.Write("@");
            }
        }
        public void makeFood()
        // 맵 20 80 -> 맵 끝쪽은 피해야됨 -> (0, 20) + (0, 80)에서
        // (2, 18) + (2, 78)에 생성되야됨
        {
            Random rand = new Random();

            if (foodCnt == 0)
            {
                foodX = rand.Next(2, 78);
                foodY = rand.Next(2, 18);
                Console.SetCursorPosition(foodX, foodY);
                Console.Write("●");
                foodCnt = foodCnt + 1;
            }
        }
        public void ChangeDir(ConsoleKeyInfo key)
        {
            if (key.Key == ConsoleKey.LeftArrow && snakeDir != 0)
                snakeDir = 2;
            else if (key.Key == ConsoleKey.UpArrow && snakeDir != 3)
                snakeDir = 1;
            else if (key.Key == ConsoleKey.RightArrow && snakeDir != 2)
                snakeDir = 0;
            else if (key.Key == ConsoleKey.DownArrow && snakeDir != 1)
                snakeDir = 3;
            else if (key.Key == ConsoleKey.Escape)
                Environment.Exit(0);
        }

        public void MoveSnake()
        {
            int[] newHead = { snakeBody[0][0], snakeBody[0][1] };
            if (snakeDir == 0) newHead[0]++;
            else if (snakeDir == 1) newHead[1]--;
            else if (snakeDir == 2) newHead[0]--;
            else if (snakeDir == 3) newHead[1]++;

            snakeBody.Insert(0, newHead);

            // 이전 꼬리 지우기
            Console.SetCursorPosition(snakeBody[snakeBody.Count - 1][0], snakeBody[snakeBody.Count - 1][1]);
            Console.Write(" ");

            // 음식 먹기
            if (snakeBody[0][0] == foodX && snakeBody[0][1] == foodY)
            {
                foodCnt--;
                gameSpeed = gameSpeed - 10;
                score++;
            }
            else
            {
                snakeBody.RemoveAt(snakeBody.Count - 1);
            }

            DrawSnake();

        }
        public void drawScore()
        {
            Console.SetCursorPosition(28, 20);
            Console.Write($"snake가 먹은 food의 수 {score}개");
        }

        public void IsColl()
        {
            // 벽과 충돌 했는가
            if (snakeBody[0][0] == 0 || snakeBody[0][0] == width - 1 ||
                snakeBody[0][1] == 0 || snakeBody[0][1] == height - 1)
            {
                Console.SetCursorPosition(35, 8);
                Console.WriteLine("Game Over!");
                Console.SetCursorPosition(21, 10);
                Console.WriteLine("게임을 종료하려면 아무키나 눌러주세요");
                Console.ReadLine();
                Environment.Exit(0);
            }

            // 몸과 충돌 했는가
            for (int i = 1; i < snakeBody.Count; i++)
            {
                if (snakeBody[0][0] == snakeBody[i][0] && snakeBody[0][1] == snakeBody[i][1])
                {
                    Console.SetCursorPosition(35, 8);
                    Console.WriteLine("Game Over!");
                    Console.SetCursorPosition(21, 10);
                    Console.WriteLine("게임을 종료하려면 아무키나 눌러주세요");
                    Console.ReadLine();
                    Environment.Exit(0);
                }
            }
        }
    }
}

 

 

간단하게 넘어갈 것으로 생각했던 3주차의 과제인데

첫 번째였던 스네이크 게임부터 막혀서 어떻게 해야되나 고민을 많이 했는데, 

구현할 내용의 순서를 정하고,

해당 순서에 따라서 코딩을 하면서 문제가 생기면 그 시점에서 수정하며 코딩했다.

특히, 미리 주석으로 구현할 내용들을 적어놓고 하나하나 코딩하니 보다 쉽게 진행할 수 있었다.

(주석으로 가이드 라인을 미리 잡아놓고 작성하니 좋았다)

특히, 강의에서 배웠던 F9를 이용하여 디버그 시점을 잡고 F10으로 한줄한줄 실행하며

문제가 생기는 부분을 파악하는 방법은 이번 과제를 만들며 제대로 학습할 수 있었다.

 

 

유니티를 배우면서 C#은 조금 소홀한 느낌도 있었고,

개념을 배워도 구체화시키기 어려웠는데, 이번 스네이크 게임을 만들면서

구체적이지 않았던 배열과 클래스, 접근 제한자, Visual Studio 이용법 등을

좀 더 구체적으로, 실용적으로 학습할 수 있었다.

 

 

블랙잭 게임은 오늘 어느정도 밑작업은 끝마쳤는데,

과제에서 코드를 어느 정도 제공해 준 것을 기반으로 하여서 게임을 만들었다.

아직 게임을 만드는 과정이라 해결하지 못한 오류가 많은데,

내일 오전에 블랙잭 게임을 완성하고, C# 강의를 내일 중으로 마칠 예정이다.