C# 스네이크게임 만들기

2025. 10. 9. 19:32개발

 
 
 전체코드

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




namespace SnakeGame
{
    
    enum Direction
    {
        UP,
        DOWN,
        LEFT,
        RIGHT
    }

    class Snake
    {
        //뱀 위치 배열
        static int[,] snake = new int[50, 2];
        //뱀 길이
        static int snakeLength = 1;
        //뱀 방향
        static Direction direction = Direction.RIGHT;
        //먹이 좌표->랜덤 생성
        static int foodX, foodY;
        //게임판 크기
        static int width = 20;
        static int height = 20;
        //게임 오버 상태
        static bool gameOver = false;
        

    static void Main(string[] args)
        {
            InitGame();

            while (!gameOver)
            {
                Input();
                Logic();
                Draw();
                Thread.Sleep(150);
            }
            Console.Clear();
            Console.WriteLine("Game Over");
        }

        static void InitGame()//초기세팅
        {
            width = 20;
            height = 20;
            
            //뱀 머리 위치 설정
            snake[0, 0] = width / 2;
            snake[0, 1] = height / 2;
            //먹이 위치 설정
            Random random = new Random();
            foodX = random.Next(0, width);
            foodY = random.Next(0, height);
        }

        static void Input() //방향키
        {
            if (Console.KeyAvailable)
            {
                var key = Console.ReadKey(true);

                switch (key.Key)
                {
                    case ConsoleKey.W:
                        Snake.direction = Direction.UP;
                        break;
                    case ConsoleKey.S:
                        Snake.direction = Direction.DOWN;
                        break;
                    case ConsoleKey.A:
                        Snake.direction = Direction.LEFT;
                        break;
                    case ConsoleKey.D:
                        Snake.direction = Direction.RIGHT;
                        break;
                }
            }
        }

      
        static void Logic()//뱀 이동+충돌검사+먹이처리
        {
           //몸통 이동
           for (int i=snakeLength-1; i>0; i--)
           {
               snake[i,0]=snake[i-1,0]; //x좌표
               snake[i,1]=snake[i-1,1]; //y좌표
           }

           if (direction == Direction.RIGHT)
           {
               snake[0,0]++;
           }
           else if (direction == Direction.LEFT)
           {
               snake[0,0]--;
           }
           else if (direction == Direction.UP)
           {
               snake[0,1]--;
           }
           else if (direction == Direction.DOWN)
           {
               snake[0,1]++;
           }
           
           //만약 벽 충돌이라면 게임오버
           if (snake[0,0]<0 || snake[0,0]>=width||snake[0,1]<0||snake[0,1]>=height)
           {
               gameOver = true;
           }
           //아니라면 자기 몸을 충돌했다면 게임오버
          else
          {
              for (int i = 1; i < snakeLength; i++)
              {
                  if (snake[0,0] == snake[i,0]&&snake[0,1] == snake[i,1])
                  {
                      gameOver = true;
                      break;
                  }
              }
          }
           
           //먹이처리
           if (snake[0,0] == foodX && snake[0,1] == foodY)
           {
               snakeLength++;          // 뱀 길이 증가
               Random random = new Random();
               foodX = random.Next(0, width);   // 새 먹이 X
               foodY = random.Next(0, height);  // 새 먹이 Y
           }

        }
        static void Draw()//화면출력
        {
            Console.Clear();
            
            for (int i = 0; i < height; i++) // 행
            {
                for (int j = 0; j < width; j++) // 열
                {
                    bool printed = false;

                    // 머리 출력
                    if (i == 0 || i == height - 1 || j == 0 || j == width - 1)
                    {
                        Console.Write("#");
                        printed = true;
                    }
                    
                    else if (i == snake[0,1] &&j == snake[0,0])
                    {
                        Console.Write("0");
                        printed = true;
                    }
                    else
                    {
                        // 몸통 출력
                        for (int k = 1; k < snakeLength; k++)
                        {
                            if (i == snake[k,1] && j == snake[k,0])
                            {
                                Console.Write("o");
                                printed = true;
                                break;
                            }
                        }
                    }

                    // 먹이 출력
                    if (!printed && i == foodY && j == foodX)
                    {
                        Console.Write("*");
                        printed = true;
                    }

                    // 빈 칸
                    if (!printed)
                        Console.Write(" ");
                }
                Console.WriteLine();
            }
            
        }
    }
}

 
개념정리가 안되서 뒤로 갈 수록 너무 헷갈린다... 그래서 개념정리 하는데 그걸 응용까지 하려니 더 헷갈린다ㅜㅜ
이걸 우리가 대체 언제 배웠냐구ㅜㅜㅜㅜ
 
 
 
 

 
트러블 슈팅

 
문제점: 스네이크 게임 메인 게임루프 while문에서 조건 고민
 
while(true)로 하면 루프 안에서 break; 쓰기 전에 무한반복이고,
while(!gameOver)를 쓰면 게임이 끝나지 않았을 때만 반복된다.
 
 

while (true)
{
    MoveSnake();
    CheckCollision();

    if (충돌발생)
        break;  // 직접 루프를 끊어야 함
}

 
while (true) 버전
➡ 단점:
게임이 끝났는지 여부를 루프 안에서
직접 break로 관리해야 함.
 
 

while (!gameOver)
{
    MoveSnake();
    CheckCollision();
}

 
while(!gameOver)버전
 
같은 함수 안에서 gameOver=true;
만 해주면 루프가 자동으로 종료됨
 
 
해결:
 
!gameOver는 “게임이 진행 중일 때만 반복” 이라는 의미로,
코드의 가독성과 관리가 훨씬 깔끔하다.
 
 
 
 
 
 
트러블 슈팅 2
 

 static void Input() //방향키
        {
            if (Console.KeyAvailable)
            {
                var key = Console.ReadKey(true);

                switch (key.Key)
                {
                    case ConsoleKey.W:
                        Snake.direction = Direction.UP;
                        break;
                    case ConsoleKey.S:
                        Snake.direction = Direction.DOWN;
                        break;
                    case ConsoleKey.A:
                        Snake.direction = Direction.LEFT;
                        break;
                    case ConsoleKey.D:
                        Snake.direction = Direction.RIGHT;
                        break;
                }
            }
        }

 
 
문제점
 
여기 switch문 안에 조건을 key로 하니까 에러났다.
Console.ReadKey()는 ConsoleKeyInfo구조체를 반환한다.
 
실제 키 값은 key.key 속성에 들어있다.
 
해결:
 
switch문의 조건을 key.key로 바꾸니까 해결 되었다.
 
 
 
 
 
 
 
 
 
 
 
 
 
오늘 알게 된 중요한 것!!
 
 
전역변수: 클래스 안에서 정의되고, 모든 매서드에 접근 가능함

지역변수메서드 안선언된 메서드 안메서드 실행 동안
전역변수클래스 안클래스 내 모든 메서드클래스 존재 동안

 
 
그러면 앞에다가 static 을 붙이면 전역변수가 될까?
답은 no!!!
 

1️⃣ static이란?

  • **정적(static)**이라는 뜻
  • 클래스에 속하고 모든 인스턴스가 공유
  • 클래스가 메모리에 올라갈 때 한 번만 만들어짐
  • 인스턴스 없이 접근 가능
class Program
{
    static int count = 0; // static 변수

    static void Main()
    {
        count++; // 클래스 전체에서 공유됨
        Console.WriteLine(count);
    }

    static void AnotherMethod()
    {
        count++; // 같은 count를 공유
        Console.WriteLine(count);
    }
}

전역변수가 무조건 static은 아님. 인스턴스 필드도 전역처럼 쓸 수 있지만, 각 객체마다 별도 존재
 
 
 

2️⃣ 차이점 요약


  static 전역변수 (클래스 필드)
존재 방식 클래스에 속함 클래스에 속함
인스턴스 없어도 접근 가능 인스턴스 변수면 객체 필요
공유 모든 메서드/객체 공유 객체마다 다를 수 있음
용도 한 번만 만들어서 공유하고 싶을 때 메서드 전체에서 데이터 공유하고 싶을 때

즉:

  • static 변수 = 전역처럼 공유 가능한 변수
  • 전역변수 = 클래스 안 어디서든 접근 가능한 변수
  • static 전역변수는 완전히 “진짜 전역”처럼 사용할 수 있음.