주의 ! 코드 전체가 나오니 과제로 만드는 분들은 참고하지 않는걸 추천드립니다.
금일은 스네이크 게임을 만들어보면서 중요하게 작용했던 몸통구현 코드를 리뷰해보려고 합니다.
전체코드는 최하단에 기재하겠습니다.
머리의 움직임을 저장해줄 리스트를 만들어주고 int값을 머리좌표로 설정해준다.
주석을 달아놔서 큰 설명을 할게 없어보인다.
해당코드를 통해 먹이를 먹으면 리스트를 추가하고 먹지 않았을때 몸통이 있다면 꼬리부분을 맵에서 0값(빈칸)으로 바꿔주고 리스트에서 0값을 삭제해준다.
저장방식이 a(꼬리 0 1 2 3 4 머리) 움직이면 > 0을삭제 b(꼬리 1 2 3 4 5머리)
하지만 0이 있어야하니 다시 c(꼬리 0 1 2 3 4머리)가 된다.
a 가 움직이면서 b가되고 b=c 라고 보면된다 하지만 사실 b는 설명을 위한것이지 없는것이다.
그리고 맵을 그리는 코드에서 포리치이용해서 몸통을 그려줌
이해가 안되는부분을 아래의 전체 코드를 보고 대부분의 주석을 달아놓았으니
코드의 흐름을 이해하고 다시 보면 좋을 것 같다.
아.. 오늘 처음으로 코드블럭이라는걸 배웠다...
using System.Reflection;
using System.Security.Cryptography;
namespace snakeGame
{
internal class Program
{
// 맵을 2차원 배열로 정의
private static int[,] map = new int[9, 9]
{
{ 3 ,3 ,3 ,3 ,3, 3 ,3 ,3, 3},
{ 3 ,0 ,0 ,0 ,0, 0 ,0 ,0 ,3},
{ 3 ,0 ,0 ,0 ,0, 0 ,0 ,0 ,3},
{ 3 ,0 ,0 ,0 ,0, 0 ,0 ,0 ,3},
{ 3 ,0 ,0 ,0 ,2, 0 ,0 ,0 ,3},
{ 3 ,0 ,0 ,0 ,0, 0 ,0 ,0 ,3},
{ 3 ,0 ,0 ,0 ,0, 0 ,0 ,0 ,3},
{ 3 ,0 ,0 ,0 ,0, 0 ,0 ,0 ,3},
{ 3 ,3 ,3 ,3 ,3, 3 ,3 ,3 ,3}
};
// 머리의 초기 위치 설정
static int headX = 4, headY = 4;
// 몸통을 저장할 리스트 (몸통의 좌표)
static List<(int, int)> snakeBody = new List<(int, int)> { (headX, headY) };
static bool main = false; // 게임 진행 여부
static bool endGame = false; // 게임 종료 여부
static void Main()
{
Console.Clear();
Console.WriteLine("스네이크 게임에 오신걸 환영합니다. 먹이를 먹으면 몸통이 늘어나며, 머리가 몸통 혹은 벽에 닿으면 게임에서 패배하게됩니다.");
Console.WriteLine();
Console.WriteLine("승리하기 위해서는 모든 맵을 당신의 뱀으로 덮어보세요 ! 시간제한은 없으니 행동 한번한번이 매우 중요합니다.");
Console.WriteLine();
Console.Write("시작하려면 1을 입력해주세요 : ");
int inputStart = int.Parse(Console.ReadLine());
// 게임 시작 조건 확인
if (inputStart == 1)
{
main = true; // 게임 시작
}
else
{
Main(); // 잘못된 입력 시 재호출
}
// 게임 루프
while (main)
{
EndGame(); // 게임 승리 여부 확인
Console.Clear(); // 콘솔 화면 초기화
GameBoard(); // 현재 맵 표시
// 사용자 입력 받기
Console.WriteLine("좌 : a, 우 : d, 위 : w, 아래 : s, 머리 : ●, 몸통 : ■, 먹이 : ◆, 벽 : ※"); // 설명
ConsoleKeyInfo keyInfo = Console.ReadKey(true);
switch (keyInfo.Key)
{
case ConsoleKey.W:
headX--; // 위로 이동
break;
case ConsoleKey.S:
headX++; // 아래로 이동
break;
case ConsoleKey.A:
headY--; // 왼쪽으로 이동
break;
case ConsoleKey.D:
headY++; // 오른쪽으로 이동
break;
}
// 게임 실패 체크 (벽이나 몸통에 부딪힌 경우)
if (map[headX, headY] == 3 || map[headX, headY] == 1)
{
Console.WriteLine("게임이 끝났습니다. 머리가 몸 혹은 벽에 부딪혔습니다.");
Environment.Exit(0);
}
// 먹이를 먹었을 때
if (map[headX, headY] == 4)
{
snakeBody.Add((headX, headY)); // 몸통에 현재 위치 추가
map[headX, headY] = 2; // 머리 위치 설정
}
else // 먹이를 먹지 않았을 때
{
// 몸통의 꼬리를 이동시키기
if (snakeBody.Count > 0)
{
var tail = snakeBody[0]; // 현재 꼬리의 위치
map[tail.Item1, tail.Item2] = 0; // 꼬리 위치 비우기
snakeBody.RemoveAt(0); // 꼬리 삭제
}
// 머리 위치를 몸통에 추가
snakeBody.Add((headX, headY));
map[headX, headY] = 2; // 머리 위치 설정
}
}
}
static void EndGame() // 게임 승리 체크
{
endGame = true; // 초기화
//반복문으로 모든 맵체크
for (int i = 0; i < map.GetLength(0); i++)
{
for (int j = 0; j < map.GetLength(1); j++)
{
if (map[i, j] == 0) // 빈 공간이 있으면
{
endGame = false; // 게임 종료 아님
}
}
}
// 빈 공간이 없으면 승리
if (endGame)
{
Console.WriteLine("빈칸이 없습니다. 게임을 승리하셨습니다.");
Environment.Exit(0);
}
}
static void GameBoard()
{
// 먹이 생성 여부 확인
bool eat = false; // 초기화
//반복문으로 모든 맵 체크
for (int i = 0; i < map.GetLength(0); i++)
{
for (int j = 0; j < map.GetLength(1); j++)
{
if (map[i, j] == 4) // 먹이가 있으면
{
eat = true; // 먹이가 있다는 표시
}
}
}
// 먹이가 없으면 랜덤으로 생성
if (!eat)
{
RandomEat(); // 랜덤 먹이 생성 함수
}
// 맵을 그리는 코드
for (int i = 0; i < map.GetLength(0); i++)
{
for (int j = 0; j < map.GetLength(1); j++)
{
if (map[i, j] == 1) // 몸통
{
Console.Write("■ ");
}
else if (map[i, j] == 2) // 머리
{
Console.Write("● ");
}
else if (map[i, j] == 3) // 벽
{
Console.Write("※ ");
}
else if (map[i, j] == 4) // 먹이
{
Console.Write("◆ ");
}
else // 빈 공간
{
Console.Write("□ ");
}
}
Console.WriteLine();
}
// 몸통을 맵에 반영
foreach (var segment in snakeBody)
{
map[segment.Item1, segment.Item2] = 1; // 몸통은 1로 표시
}
}
static void RandomEat() // 랜덤 먹이 생성 함수
{ //반복을 통해 빈곳을 찾을때까지 랜덤으로 돌림 (굉장히 더러운코드 ㅠㅠ)
while (true)
{
Random random = new Random();
int x = random.Next(0, 9);
int y = random.Next(0, 9);
if (map[x, y] == 0) // 빈 공간에만 생성
{
map[x, y] = 4; // 먹이 생성
break; // 반복 종료
}
}
}
}
}