TIL

2023/11/08 TIL

미역제자 2023. 11. 8. 21:03


이번주 내로 강의를 5주차까지 다 들어야 하는줄 알고 서둘렀는데 3주차까지만 들으면 되는 거였다.

그래서 오늘은 틱택토, 뱀 게임 코드를 살펴볼 것이다.

더보기

틱택토 게임 코드

namespace _2_6
{
    //TICTAKTOE 만들기
    internal class Program
    {
        static char[] arr = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; //0부터 9까지의 배열
        static int player = 1;
        static int choice;
        static int flag = 0;

        static void Main(string[] args)
        {
            do //do while문. 무조건 한번 실행하고 반복
            {
                Console.Clear();
                Console.WriteLine("플레이어 1: X 와 플레이어 2: O");
                Console.WriteLine("\n");

                if (player % 2 == 0) //차례 확인
                { //2번의 차례이면
                    Console.WriteLine("플레이어 2의 차례");
                }
                else
                { //1번의 차례이면
                    Console.WriteLine("플레이어 1의 차례");
                }

                Console.WriteLine("\n");
                Board(); //보드 그리기

                string line = Console.ReadLine(); //사용자로부터 콘솔에서 한 줄을 입력받아와 문자열로 반환
                bool res = int.TryParse(line, out choice);
                // line에서 입력받은 결과를 int값으로 변환해서 변환에 성공하면 res에 true를 저장하고 변환된 값을 choice에 저장.


                if (res == true) //변환에 성공한경우
                {
                    if (arr[choice] != 'X' && arr[choice] != 'O') //선택한 숫자(choice)가 적힌 칸(arr)에 X나 0이 없는 경우
                    {
                        if (player % 2 == 0)
                        {//2번의 차례이면
                            arr[choice] = 'O';
                        }
                        else
                        {//1번의 차례이면
                            arr[choice] = 'X';
                        }

                        player++; //다음턴(player에 1 추가)
                    }
                    else // 이미 선택했던 칸인경우
                    {
                        Console.WriteLine("죄송합니다. {0} 행은 이미 {1}로 표시되어 있습니다.", choice, arr[choice]); 
                        // [choice]행은 0(또는 X)로 표시되어있습니다.
                        Console.ReadLine();
                    }
                }
                else // 정해진 숫자가 아니거나 문자를 입력한 경우
                {
                    Console.WriteLine("숫자를 입력해주세요.");
                }

                flag = CheckWin(); //게임의 승리여부
            }
            while (flag != -1 && flag != 1); //반복문. flag가 -1이 아니거나 1이 아닌경우 반복.

            if (flag == 1) // flag가 1인경우 : 누군가 승리했을때
            {
                Console.WriteLine("플레이어 {0}이(가) 이겼습니다.", (player % 2) + 1); //턴이 넘어간 상태에서 뜨는 메세지이기 때문에 +1을 더함.
            }
            else // flag가 -1인 경우 : 무승부
            {
                Console.WriteLine("무승부");
            }

            Console.ReadLine();
        }

        static void Board()
        {
            Console.WriteLine("     |     |     ");
            Console.WriteLine("  {0}  |  {1}  |  {2}  ", arr[1], arr[2], arr[3]);
            Console.WriteLine("_____|_____|_____");
            Console.WriteLine("     |     |     ");
            Console.WriteLine("  {0}  |  {1}  |  {2}  ", arr[4], arr[5], arr[6]);
            Console.WriteLine("_____|_____|_____");
            Console.WriteLine("     |     |     ");
            Console.WriteLine("  {0}  |  {1}  |  {2}  ", arr[7], arr[8], arr[9]);
            Console.WriteLine("     |     |     ");
        }

        static int CheckWin() //승리조건 체크
        {
            // 가로 승리 조건
            if (arr[1] == arr[2] && arr[2] == arr[3]) 
            {
                return 1;
            }
            else if (arr[4] == arr[5] && arr[5] == arr[6])
            {
                return 1;
            }
            else if (arr[7] == arr[8] && arr[8] == arr[9])
            {
                return 1;
            }

            // 세로 승리 조건
            else if (arr[1] == arr[4] && arr[4] == arr[7])
            {
                return 1;
            }
            else if (arr[2] == arr[5] && arr[5] == arr[8])
            {
                return 1;
            }
            else if (arr[3] == arr[6] && arr[6] == arr[9])
            {
                return 1;
            }

            // 대각선 승리조건
            else if (arr[1] == arr[5] && arr[5] == arr[9])
            {
                return 1;
            }
            else if (arr[3] == arr[5] && arr[5] == arr[7])
            {
                return 1;
            }

            // 무승부
            else if (arr[1] != '1' && arr[2] != '2' && arr[3] != '3' && arr[4] != '4' && arr[5] != '5' && arr[6] != '6' &&
                arr[7] != '7' && arr[8] != '8' && arr[9] != '9') //모든 칸이 체크가 되어있고, 승리조건에 일치하는 항목이 없을 때.
            {
                return -1;
            }
            else { return 0; } //게임이 끝나지 않았으니 계속.

        }
    }
}

엄청 어려운 코드는 아니긴 했다.

 static char[] arr = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };

 

 

이 한줄이 이해가 안갔는데, 왜 9칸인데 배열은 10개를 했는가이다.

배열에서 0을 제외해보니 빌드 오류가 나고, 그렇다고 9를 제외할 수는 없었다.

 

System.IndexOutOfRangeException: 'Index was outside the bounds of the array.'

인덱스 범위를 벗어났다는 오류가 나오는 것이다. 

 

배열을 10개를 유지하면서 9개만 사용하는 이유를 모르겠다.


다음은 뱀 게임(Snake Game)이다.

더보기

뱀 게임 코드

class Program
{
    static void Main(string[] args)
    {
        // 게임 속도를 조정하기 위한 변수입니다. 숫자가 클수록 게임이 느려집니다.
        int gameSpeed = 100;
        int foodCount = 0; // 먹은 음식 수

        // 게임을 시작할 때 벽을 그립니다.
        DrawWalls();

        // 뱀의 초기 위치와 방향을 설정하고, 그립니다.
        Point p = new Point(4, 5, '*'); // p 점의 위치 
        Snake snake = new Snake(p, 4, Direction.RIGHT); // 초기 설정 (p점을 꼬리, 길이는 4, 방향은 오른쪽)
        snake.Draw(); //Draw를 실행.

        // 음식의 위치를 무작위로 생성하고, 그립니다.
        FoodCreator foodCreator = new FoodCreator(80, 20, '$'); //너비 80, 높이 20인 범위 내에서 랜덤 좌표 생성, 심볼은 $
        Point food = foodCreator.CreateFood(); //랜덤으로 나온 좌표를 food란 점에 저장
        food.Draw(); //food그리기

        // 게임 루프: 이 루프는 게임이 끝날 때까지 계속 실행됩니다.
        while (true)
        {
            // 키 입력이 있는 경우에만 방향을 변경합니다.
            if (Console.KeyAvailable)
            {
                var key = Console.ReadKey(true).Key; //읽은 방향키를 key에 저장

                switch (key)
                {
                    case ConsoleKey.UpArrow: //위쪽 방향키를 입력했을때
                        snake.direction = Direction.UP;
                        break;
                    case ConsoleKey.DownArrow: //아래쪽 방향키를 입력했을때
                        snake.direction = Direction.DOWN;
                        break;
                    case ConsoleKey.LeftArrow: //왼쪽 방향키를 입력했을때
                        snake.direction = Direction.LEFT;
                        break;
                    case ConsoleKey.RightArrow: //오른쪽 방향키를 입력했을때
                        snake.direction = Direction.RIGHT;
                        break;
                }
            }

            // 뱀이 음식을 먹었는지 확인합니다.
            if (snake.Eat(food)) //Eat에서 true가 나왔을 경우
            {
                foodCount++; // 먹은 음식 수를 증가
                food.Draw(); // 새로운 몸을 그림.

                // 뱀이 음식을 먹었다면, 새로운 음식을 만들고 그립니다.
                food = foodCreator.CreateFood(); //새로운 랜덤좌표 저장
                food.Draw(); //음식 생성
                if (gameSpeed > 10) // 게임이 점점 빠르게
                {
                    gameSpeed -= 10; //10씩 난이도 증가
                }
            }
            else
            {
                // 뱀이 음식을 먹지 않았다면, 그냥 이동합니다.
                snake.Move();
            }

            Thread.Sleep(gameSpeed); //gameSpeed만큼 게임 일시정지. gameSpeed가 줄어든 만큼 일시정지 시간이 줄어들어서 난이도 상승

            // 벽이나 자신의 몸에 부딪히면 게임을 끝냅니다.
            if (snake.IsHitTail() || snake.IsHitWall()) //둘 중 하나라도 해당 될 경우
            {
                break;
            }

            Console.SetCursorPosition(0, 21); // 커서 위치 설정
            Console.WriteLine($"먹은 음식 수: {foodCount}"); // 먹은 음식 수 출력
        }

        WriteGameOver();  // 게임 오버 메시지를 출력합니다.
        Console.ReadLine();
    }

    //게임 오버 메세지
    static void WriteGameOver()
    {
        int xOffset = 25;
        int yOffset = 22;
        Console.SetCursorPosition(xOffset, yOffset++);
        WriteText("============================", xOffset, yOffset++);
        WriteText("         GAME OVER", xOffset, yOffset++);
        WriteText("============================", xOffset, yOffset++);
    }

    static void WriteText(string text, int xOffset, int yOffset)
    {
        Console.SetCursorPosition(xOffset, yOffset);
        Console.WriteLine(text);
    }

    // 벽 그리는 메서드
    static void DrawWalls()
    {
        // 상하 벽 그리기
        for (int i = 0; i < 80; i++)
        {
            Console.SetCursorPosition(i, 0);
            Console.Write("#");
            Console.SetCursorPosition(i, 20);
            Console.Write("#");
        }

        // 좌우 벽 그리기
        for (int i = 0; i < 20; i++)
        {
            Console.SetCursorPosition(0, i);
            Console.Write("#");
            Console.SetCursorPosition(80, i);
            Console.Write("#");
        }
    }
}


//Point Class
public class Point
{
    public int x { get; set; }
    public int y { get; set; }
    public char sym { get; set; }

    // Point 클래스 생성자
    public Point(int _x, int _y, char _sym)
    { //설정한 x, y, sym을 가져와 저장.
        x = _x;
        y = _y;
        sym = _sym;
    }

    // 점을 그리는 메서드
    public void Draw() // 콘솔 특정 위치에 문자 sym을 그리는 함수를 나타냄.
    {
        Console.SetCursorPosition(x, y); // 콘솔상 출력위치를 xy를 이용해 나타냄
        Console.Write(sym); //해당 위치에 문자sym을 나타냄.
    }

    // 점을 지우는 메서드
    public void Clear()
    { 
        sym = ' ';
        Draw(); // Clear()을 사용하면 특정 위치를 공백으로 채움
    }

    // 두 점이 같은지 비교하는 메서드
    public bool IsHit(Point p)
    {
        return p.x == x && p.y == y;
    }
}
// 방향을 표현하는 열거형입니다.
public enum Direction
{
    LEFT,
    RIGHT,
    UP,
    DOWN
}

//Snake class
public class Snake
{
    public List<Point> body; // 뱀의 몸통을 Point들의 리스트로 표현합니다.
    public Direction direction; // 뱀의 현재 방향을 저장합니다.

    public Snake(Point tail, int length, Direction _direction) // 꼬리의 위치, 길이, 방향
    {
        direction = _direction;
        body = new List<Point>();
        for (int i = 0; i < length; i++)
        { //i = 0부터, 길이보다 작을때만, 1씩 증가.
            Point p = new Point(tail.x, tail.y, '*'); //Point p는 tail이란 포인트의 x, y, sym을 가져옴
            body.Add(p); //몸체에 p점을를 더함.
            tail.x += 1; //tail의 x좌표에 1을 더함
        }
    }

    // 뱀을 그리는 메서드입니다.
    public void Draw() //snake.draw
    {
        foreach (Point p in body) //p점이 몸 안에 있을 때,
        {
            p.Draw(); //point.Draw를 이용해 p점을 그림.
        }
    }

    // 뱀이 음식을 먹었는지 판단하는 메서드입니다.
    public bool Eat(Point food) 
    {
        Point head = GetNextPoint();
        if (head.IsHit(food)) //head 점이 food점과 만났을때
        {
            food.sym = head.sym; //음식 표시를 head 표시로 변경
            body.Add(food); //몸에 food점을 추가
            return true; // Eat에 true 반환
        }
        else
        {
            return false;
        }
    }

    // 뱀이 이동하는 메서드입니다.
    public void Move()
    {
        Point tail = body.First(); //body 배열의 첫번째칸이 tail이됨
        body.Remove(tail); //body라는 배열에서 tail을 지움-> body배열의 끝을 제거
        Point head = GetNextPoint();//다음칸 위치를 head에 저장
        body.Add(head); //body라는 배열에서 head를 추가 -> tail을 지우고 head를 추가->이동하는 것 처럼 보임.

        tail.Clear(); //꼬리에 있던 점을 지움
        head.Draw(); //머리 점을 그리기
    }

    // 다음에 이동할 위치를 반환하는 메서드입니다.
    public Point GetNextPoint()
    {
        Point head = body.Last(); //body배열의 마지막 칸을 head라는 점에 저장
        Point nextPoint = new Point(head.x, head.y, head.sym); //nextPoint라는 점 생성
        switch (direction) //direction에 대한 switch case
        {
            case Direction.LEFT: //왼쪽으로 움직일때
                nextPoint.x -= 2; //nextPoint의 x좌표를 -2
                break;
            case Direction.RIGHT: //오른쪽으로 움직일때
                nextPoint.x += 2; //nextPoint의 x좌표를 2더함
                break; //x좌표는 2씩 더하는 이유: console 창의 x칸이 좁기 때문.
               
            case Direction.UP: //위쪽으로 움직일때
                nextPoint.y -= 1; //nextPoint의 y좌표를 -1 더함
                break;
            case Direction.DOWN: //아래쪽으로 움직일때
                nextPoint.y += 1; //nextPoint의 y좌표를 1 더함
                break;
        }
        return nextPoint; //위에서 바뀐 점(nextPoint)을 내뱉음
    }

    // 뱀이 자신의 몸에 부딪혔는지 확인하는 메서드입니다.
    public bool IsHitTail()
    {
        var head = body.Last(); //body 배열의 끝을 head로 둠.
        for (int i = 0; i < body.Count - 2; i++) //head에서 2칸 아래부터 몸통부분
        {
            if (head.IsHit(body[i])) //몸통 부분의 모든 부분 중 하나라도 head와 겹칠 때
                return true; //몸통과 부딪혔다 true
        }
        return false;
    }

    // 뱀이 벽에 부딪혔는지 확인하는 메서드입니다.
    public bool IsHitWall()
    {
        var head = body.Last(); //머리 좌표 가져옴
        if (head.x <= 0 || head.x >= 80 || head.y <= 0 || head.y >= 20) //벽이 있는 부분들 좌표를 넘어갈 때
            return true;
        return false;
    }
}

public class FoodCreator
{
    int mapWidth; //맵의 넓이
    int mapHeight; //맵의 높이
    char sym; //상징

    Random random = new Random(); //랜덤성 추가

    public FoodCreator(int mapWidth, int mapHeight, char sym) // 넓이, 높이, 상징
    { 
        this.mapWidth = mapWidth;
        this.mapHeight = mapHeight;
        this.sym = sym;
    }

    // 무작위 위치에 음식을 생성하는 메서드입니다.
    public Point CreateFood()
    {
        int x = random.Next(2, mapWidth - 2); // 2 부터 맵의 넓이-2 사이 좌표의 x좌표
        // x 좌표를 2단위로 맞추기 위해 홀수로 만듭니다.
        x = x % 2 == 1 ? x : x + 1; // '%'를 이용해서 나머지가 1인경우(홀수) x유지, 나머지가 0인경우(짝수) 부정이 되어서 x + 1 -> 전부 홀수
        int y = random.Next(2, mapHeight - 2); //y 좌표는 범위 내 랜덤
        return new Point(x, y, sym); //음식이 나오는 좌표 생성.
    }
}

 

 

이 코드 때문에 오늘 하루를 다 날렸다.

 

혼자 게임을 만들다 멘탈이 터지고 결국 예시로 나온 코드를 보며 하나하나 분석을 해보았다.

뱀의 길이를 늘리는 방법을 구현하다 막히고, 뱀의 이동을 구현하다가도 막혔다.

 

이런 간단한 게임들을 만드는데도 어떻게 만들어 나가야 할지 갈피가 잘 안잡혔다.

비어있는 visual studio를 보면 머리도 같이 하얘지는 느낌이다.

 

기초틀 부터 쌓아 올리는 부분이 어려워 튜터님께 여쭤보니 경험이 좀 쌓이면 나아질거라고 해주셨다.

또 그런 부분들을 적어 가면서, 그걸 해결해 나가는 경험이 중요하다고 하셨다.

튜터님은 흰 종이에 적어가면서 풀었다고 하셨다.

화이팅 해야겠다.

'TIL' 카테고리의 다른 글

2023/11/10 TIL  (0) 2023.11.10
2023/11/09 TIL  (2) 2023.11.09
2023/11/07 TIL  (0) 2023.11.07
2023/11/06 TIL  (0) 2023.11.06
2023/11/03 TIL  (1) 2023.11.03