Snake게임이 생각보다 구현이 잘 되서 공유한다.
namespace ConsoleRPG
{
internal class Program
{
public class pos
{
int x;
int y;
public pos()
{
x = 0;
y = 0;
}
public pos(int X, int Y)
{
this.x = X;
this.y = Y;
}
public pos(pos P)
{
this.x = P.getX();
this.y = P.getY();
}
public int getX() { return x; }
public int getY() { return y; }
public void setX(int X) { x = X; }
public void setY(int Y) { y = Y; }
public void AddPos(pos P)
{
this.x += P.getX();
this.y += P.getY();
}
}
class Snake
{
int size = 4;
public pos headPos = new pos(0,0);
public enum Direction
{
Up,
Down,
Left,
Right
}
public Direction dir;
public List<pos> snakePos = new List<pos>();
public Snake()
{
this.size = 4;
this.headPos = new pos(0,0);
this.dir = Direction.Right;
this.snakePos = new List<pos>();
}
public Snake(int boardsize)
{
this.size = 4;
this.headPos = new pos(boardsize / 2, boardsize / 2);
this.dir = Direction.Right;
this.snakePos = new List<pos>();
for (int i = 0; i < this.size; i++)
{
pos p = new pos(-i, 0);
p.AddPos(this.headPos);
snakePos.Add(p);
}
}
public bool UpdateSnake(Board b, Direction d)
{
pos temphead;
switch (d)
{
case Direction.Left:
temphead = new pos(-1, 0);
break;
case Direction.Right:
temphead = new pos(1, 0);
break;
case Direction.Up:
temphead = new pos(0, -1);
break;
case Direction.Down:
temphead = new pos(0, 1);
break;
default:
temphead = new pos(0, 0);
break;
}
this.dir = d;
temphead.AddPos(this.headPos);
if (temphead.getX() < 0 || temphead.getY() < 0 || temphead.getX() >= b.size || temphead.getX() >= b.size)
return false;
if (temphead.getX() == b.snackPos.getX() && temphead.getY() == b.snackPos.getY())
{
this.size++;
b.board[b.snackPos.getY(), b.snackPos.getX()] = '.';
b.GenSnackPos();
snakePos.Insert(0, temphead);
headPos = snakePos[0];
}
else
{
snakePos.Insert(0, temphead);
headPos = snakePos[0];
snakePos.RemoveAt(this.size);
}
return true;
}
}
class Board
{
public char[,] board;
public int size = 10;
public pos snackPos = new pos(0,0);
Random rng = new Random();
public Board()
{
board = new char[size, size];
snackPos = new pos(-1, -1);
}
public Board(int size)
{
this.size = size;
board = new char[this.size, this.size];
for (int i = 0; i < this.size; i++)
for (int j = 0; j < this.size; j++)
board[i, j] = '.';
GenSnackPos();
}
public void GenSnackPos()
{
bool isFull = true;
for (int i = 0; i < this.size; i++)
{
for (int j = 0; j < this.size; j++)
{
if (board[i, j] == '.' || board[i, j] == '*')
{
isFull = false;
break;
}
}
if (!isFull)
break;
}
if (isFull)
{
Console.WriteLine("Wait I can't believe you beat the game!");
return;
}
do
{
int x = rng.Next(0, size);
int y = rng.Next(0, size);
if (board[y, x] == '.')
{
snackPos.setY(y);
snackPos.setX(x);
board[y, x] = '*';
break;
}
} while (true);
}
public bool SetSnake(Snake s)
{
foreach (pos p in s.snakePos)
{
if (p.getX() < 0 || p.getY() < 0 || p.getX() >= this.size || p.getY() >= this.size)
return false;
if (board[p.getY(), p.getX()] == 'O')
return false;
board[p.getY(), p.getX()] = 'O';
}
board[s.headPos.getY(), s.headPos.getX()] = 'X';
return true;
}
public bool ResetBoard(Snake s)
{
for (int i = 0; i < this.size; i++)
for (int j = 0; j < this.size; j++)
board[i, j] = '.';
if (!SetSnake(s))
return false;
board[snackPos.getY(), snackPos.getX()] = '*';
return true;
}
public void WriteBoard()
{
for (int i = 0; i < this.size; i++)
{
for (int j = 0; j < this.size; j++)
{
Console.Write(board[i, j]);
}
Console.WriteLine();
}
}
}
static void Main(string[] args)
{
int size = 10;
Snake snake = new Snake(size);
Board board = new Board(size);
do
{
if (!board.ResetBoard(snake))
{
Console.WriteLine("Game Ended");
break;
}
board.WriteBoard();
Console.WriteLine();
Console.WriteLine("Where you want to Move ? ");
int input = 0;
do
{
Console.Write("(4: left | 6 : right | 8 : Up | 2 : Down | 0 : Skip Turn) : ");
string str = Console.ReadLine();
if (!int.TryParse(str, out input) || (input != 0 && input != 2 && input != 4 && input != 6 && input != 8))
continue;
} while (false);
bool isLive = true;
if (input == 0)
{
Console.WriteLine("You did nothing this turn.");
isLive = snake.UpdateSnake(board, snake.dir);
}
else if (input == 2)
{
if (snake.dir != Snake.Direction.Up && snake.dir != Snake.Direction.Down)
Console.WriteLine("You are moving down");
else
Console.WriteLine("Invalid input, you are still going down");
isLive = snake.UpdateSnake(board, Snake.Direction.Down);
}
else if (input == 4)
{
if (snake.dir != Snake.Direction.Right && snake.dir != Snake.Direction.Left)
Console.WriteLine("You are moving left");
else
Console.WriteLine("Invalid input, you are still going left");
isLive = snake.UpdateSnake(board, Snake.Direction.Left);
}
else if (input == 6)
{
if (snake.dir != Snake.Direction.Right && snake.dir != Snake.Direction.Left)
Console.WriteLine("You are moving right");
else
Console.WriteLine("Invalid input, you are still going right");
isLive = snake.UpdateSnake(board, Snake.Direction.Right);
}
else if (input == 8)
{
if (snake.dir != Snake.Direction.Up && snake.dir != Snake.Direction.Down)
Console.WriteLine("You are moving up");
else
Console.WriteLine("Invalid input, you are still going up");
isLive = snake.UpdateSnake(board, Snake.Direction.Up);
}
if (!isLive)
{
Console.WriteLine("GameOver");
break;
}
} while (true);
}
}
}
좌표를 표시하기 위해 설정해준 값이다. 추가적으로 Addpos(Pos p)를 통한 덧셈과, 빠른 대입을 위해 생성자를 세개 만들어 주었다.
headpos는 제일 앞부분인 머리의 위치, 그리고 모든 뱀의 위치는 snakePos 라는 리스트에 저장 하였다. 삽입을 많이 할 것이기 때문에 배열 대신 리스트를 사용하였다.
direction이라는 enum을 정의하여 방향을 지정해주었지만, 매번 값을 생성해주는것을 방지하기 위해 Dictionary를 활용하는것도 좋았을 것 같다.
UpdateSnake에서 Board와 direction을 받아, 현재 가는 위치를 업데이트 해주고, 만약 간식을 먹었다면, 뱀의 길이를 늘려주고, 아니라면 뱀을 한칸씩 앞으로 이동시킨다. 반면 뱀이 벽에 부딪힐 경우 false를 반환하여 게임 종료를 확인한다.
뱀을 이동시키는 로직은 아이템을 먹었다면, 아이템의 위치에 머리를 추가해주면 되고, 그게 아니라면 아이템의 위치를 머리부분에 추가시켜 준 뒤, 제일 뒤에 있는 부분이 사라지면 된다. (현재 0번이 아이템 위치, 현재 1번이 전의 0번위치 .... 현재 n번위치는 n-1번 위치 이므로 n번째 위치만 지워주면 된다.)
보드 구현이 좀 아쉽게 되었지만, 우선 파악하자.
보드는 사이즈에 따라서 생성을 다르게 해 주었고, 기본 생성자는 사이즈를 10으로 해 주었다. 보드에서 생성자에서 간식을 생성하지 않고, snake를 받은 뒤 reset에서 실행해준다.
보드에 간식의 위치를 저장 해 주어, 간식을 먹었는지 안먹었는지를 판단해 새로 간식의 위치를 설정 해 주었다.
Reset을 통해 보드 초기화 후 뱀의 위치, 간식의 위치를 넣어준다. 이 부분이 아쉬웠던 부분인데, 모든 보드를 전부 초기화 할 필요없이, 변화된 값만 초기화 시켜주면 훨씬 빠를것이다. 이곳에서 게임 종료조건들이 확인되는데, 만약 뱀끼리 꼬이거나, 뱀이 벽에 밖았을 경우(뱀이 벽에 박는것은 UpdateSnake의 반환값을 활용한다) 게임을 종료시킨다.
WriteBoard를 통해 현재 보드를 출력해주는 로직을 작성하여 편의성을 높였다.
보드와 뱀을 생성한 뒤, 리셋을 하여 뱀과 간식을 보드에 작성한다.
그 후 보드를 출력해주고, 인풋을 받는다.
2,4,6,8,0을 받는데, 0은 방향을 바꾸지 않는것이고, 2,4,6,8은 num패드 기준으로 아래 왼 오른 위 방향이다. 물론 지근 진행방향과 수직이어야만 인풋이 의미가 있으므로, 이 작업을 추가로 해 주었다.
생각보다 잘 되어서 플레이 하기 괜찮은것 같다. 플레이 시 "*"이 간식, '.'은 빈칸, "O"는 뱀의 몸, "X"는 뱀의 머리이다.