반응형

class Player
더보기
플레이어를 움직이는 부분
namespace Algorithm
{
class Pos
{
public Pos(int y, int x) { Y = y; X = x; }
public int Y;
public int X;
}
internal class Player
{
public int posY { get; private set; }
public int posX { get; private set; }
Random _random = new Random();
Board _board;
enum Dir
{
Up = 0,
Left = 1,
Down = 2,
Right = 3
}
int _dir = (int)Dir.Up;
List<Pos> _points = new List<Pos>();
public void Initialize(int posY, int posX, Board board)
{
this.posY = posY;
this.posX = posX;
_board = board;
AStar();
}
struct PQNode : IComparable<PQNode>
{
public int F;
public int G;
public int Y;
public int X;
public int CompareTo(PQNode other)
{
if (F == other.F)
return 0;
return F < other.F ? 1 : -1;
}
}
void AStar()
{
// U L D R UL DL DR UR
int[] deltaY = new int[] { -1, 0, 1, 0, -1, 1, 1, -1 };
int[] deltaX = new int[] { 0, -1, 0, 1, -1, -1, 1, 1 };
int[] cost = new int[] { 10, 10, 10, 10, 14, 14, 14, 14 };
// 점수 매기기
// F = G + H
// F = 최종 점수 (작을수록 좋음, 경로에 따라 달라짐)
// G = 시작점에서 해당 좌표까지 이동하는데 드는 비용 (작을수록 좋음, 경로에 따라 달라짐)
// H = 목적지에서 얼마나 가까운지 (작을수록 좋음, 고정)
// (y, x) 이미 방문했는지 여부 (방문 = closed 상태)
bool[,] closed = new bool[_board.Size, _board.Size];
// (y, x) 가는 길을 한 번이라도 발견했는지
// 발견X => MaxValue
// 발견O => F = G + H
int[,] open = new int[_board.Size, _board.Size];
for(int y = 0; y < _board.Size; y++)
for(int x = 0; x < _board.Size; x++)
open[y, x] = Int32.MaxValue;
Pos[,] parent = new Pos[_board.Size, _board.Size];
// 오픈리스트에 있는 정보들 중에서, 가장 좋은 후보를 빠르게 뽑아오기 위한 도구
PriorityQueue<PQNode> pq = new PriorityQueue<PQNode>();
// 시작점 발견(예약 진행)
open[posY, posX] = 10 *( Math.Abs(_board.destY - posY) + Math.Abs(_board.destX - posX));
pq.Push(new PQNode() { F = 10 * (Math.Abs(_board.destY - posY) + Math.Abs(_board.destX - posX)), G = 0, Y = posY, X = posX });
parent[posY, posX] = new Pos(posY, posX);
while (pq.Count > 0)
{
// 제일 좋은 후보를 찾는다
PQNode node = pq.Pop();
// 동일한 좌표를 여러 경로로 찾아서, 더 빠른 경로로 인해서 이미 방문(closed)된 경우 스킵
if (closed[node.Y, node.X])
continue;
// 방문한다
closed[node.Y, node.X] = true;
// 목적지에 도착 했으면 바로 종료
if (node.Y == _board.destY && node.X == _board.destX)
break;
// 상하좌우 등 이동할 수 있는 좌표인지 확인해서 예약(open)한다
for(int i = 0; i < deltaY.Length; i++)
{
int nextY = node.Y + deltaY[i];
int nextX = node.X + deltaX[i];
// 유효 범위를 벗어났으면 스킵
if (nextX < 0 || nextX >= _board.Size || nextY < 0 || nextY >= _board.Size)
continue;
// 벽으로 막혀서 갈 수 없으면 스킵
if (_board.Tile[nextY, nextX] == Board.TileType.wall)
continue;
// 이미 방문한 곳이면 스킵
if (closed[nextY, nextX])
continue;
// 비용 계산
int g = node.G + cost[i];
int h = 10 * (Math.Abs(_board.destY - nextY) + Math.Abs(_board.destX - nextX));
// 다른 경로에서 더 빠른 길을 이미 찾았으면 스킵
if (open[nextY, nextX] < g + h)
continue;
open[nextY, nextX] = g + h;
pq.Push(new PQNode() { F = g + h, G = g, Y = nextY, X = nextX });
parent[nextY, nextX] = new Pos(node.Y, node.X);
}
}
CalcPathFromParent(parent);
}
void CalcPathFromParent(Pos[,] parent)
{
int y = _board.destY;
int x = _board.destX;
while (parent[y, x].Y != y || parent[y, x].X != x)
{
_points.Add(new Pos(y, x));
Pos pos = parent[y, x];
y = pos.Y;
x = pos.X;
}
_points.Add(new Pos(y, x));
_points.Reverse();
}
// 100밀리세컨드 == 0.1초
const int MOVE_TICK = 50;
int _sumTick = 0;
int _lastIndex = 0;
public void Update(int deltaTick)
{
if (_lastIndex >= _points.Count)
return;
_sumTick += deltaTick;
if(_sumTick >= MOVE_TICK)
{
_sumTick = 0;
posY = _points[_lastIndex].Y;
posX = _points[_lastIndex].X;
_lastIndex++;
}
}
}
}
class PriorityQueue
더보기
우선순위 큐 구현
namespace Algorithm
{
class PriorityQueue<T> where T : IComparable<T>
{
List<T> _heap = new List<T>();
public void Push(T data)
{
// 힙의 맨 끝에 새로운 데이터를 삽입한다
_heap.Add(data);
int now = _heap.Count - 1;
// 도장깨기 시작
while (now > 0)
{
// 도장깨기 시도
// 부모의 인덱스 구하기
int next = (now - 1) / 2;
if (_heap[now].CompareTo(_heap[next]) < 0)
break;
// 두 값을 교체한다
T temp = _heap[now];
_heap[now] = _heap[next];
_heap[next] = temp;
// 검사 위치 ㅣ동
now = next;
}
}
public T Pop()
{
// 반환할 데이터를 따로 저장
T ret = _heap[0];
// 마지막 데이터를 루트로 이동
int lastIndex = _heap.Count - 1;
_heap[0] = _heap[lastIndex];
_heap.RemoveAt(lastIndex);
lastIndex--;
// 역으로 내려가는 도장깨기 시작
int now = 0;
while (true)
{
int left = 2 * now + 1;
int right = 2 * now + 2;
int next = now;
// 왼쪽 값이 현재 값보다 크면, 왼쪽으로 이동
if (left <= lastIndex && _heap[next].CompareTo(_heap[left]) < 0)
next = left;
// 오른쪽 값이 현재값(왼쪽 이동 포함)보다 크면, 오른족으로 이동
if (right <= lastIndex && _heap[next].CompareTo(_heap[right]) < 0)
next = right;
// 왼쪽/오른족 모두 현재값보다 작으면 종료
if (next == now)
break;
// 두 값을 교체한다
T temp = _heap[now];
_heap[now] = _heap[next];
_heap[next] = temp;
// 검사 위치를 이동한다
now = next;
}
return ret;
}
public int Count { get { return _heap.Count; } }
}
}
class Board
더보기
게임 판을 그려주는 부분
namespace Algorithm
{
class Board
{
const char CIRCLE = '\u25cf';
public TileType[,] Tile { get; private set; }
public int Size { get; private set; }
public int destY { get; private set; }
public int destX { get; private set; }
Player _player;
public enum TileType
{
Empty,
wall
}
public void Initialize(int size, Player player)
{
if (size % 2 == 0)
return;
_player = player;
Tile = new TileType[size, size];
Size = size;
destY = Size - 2;
destX = Size - 2;
for (int y = 0; y < Size; y++)
{
for (int x = 0; x < Size; x++)
{
if (x % 2 == 0 || y % 2 == 0)
Tile[y, x] = TileType.wall;
else
Tile[y, x] = TileType.Empty;
}
}
//GenerateByBinaryTree();
GenerateBySideWinder();
}
public void GenerateBySideWinder()
{
// 랜덤으로 우측 혹은 아래로 길을 뚫는 작업
// Binary Tree Algorithm
Random rand = new Random();
for (int y = 0; y < Size; y++)
{
int count = 1;
for (int x = 0; x < Size; x++)
{
if (x % 2 == 0 || y % 2 == 0)
continue;
if (y == Size - 2 && x == Size - 2)
continue;
if (y == Size - 2)
{
Tile[y, x + 1] = TileType.Empty;
continue;
}
if (x == Size - 2)
{
Tile[y + 1, x] = TileType.Empty;
continue;
}
if (rand.Next(0, 2) == 0)
{
Tile[y, x + 1] = TileType.Empty;
count++;
}
else
{
int randomIndex = rand.Next(0, count);
Tile[y + 1, x - randomIndex * 2] = TileType.Empty;
count = 1;
}
}
}
}
void GenerateByBinaryTree()
{
// 랜덤으로 우측 혹은 아래로 길을 뚫는 작업
// Binary Tree Algorithm
Random rand = new Random();
for (int y = 0; y < Size; y++)
{
for (int x = 0; x < Size; x++)
{
if (x % 2 == 0 || y % 2 == 0)
continue;
if (y == Size - 2 && x == Size - 2)
continue;
if (y == Size - 2)
{
Tile[y, x + 1] = TileType.Empty;
continue;
}
if (x == Size - 2)
{
Tile[y + 1, x] = TileType.Empty;
continue;
}
if (rand.Next(0, 2) == 0)
{
Tile[y, x + 1] = TileType.Empty;
}
else
{
Tile[y + 1, x] = TileType.Empty;
}
}
}
}
public void Render()
{
ConsoleColor prevColor = Console.ForegroundColor;
for (int y = 0; y < Size; y++)
{
for (int x = 0; x < Size; x++)
{
// 플레이어 좌표를 가져와서, 그 좌표랑 현재 y, x가 일치하면 플레이어 전용 색상으로 표시
if(y == _player.posY && x == _player.posX)
Console.ForegroundColor = ConsoleColor.Blue;
else if(y == destY && x == destY)
Console.ForegroundColor = ConsoleColor.Yellow;
else
Console.ForegroundColor = GetTileColor(Tile[y, x]);
Console.Write(CIRCLE);
}
Console.WriteLine();
}
Console.ForegroundColor = prevColor;
}
ConsoleColor GetTileColor(TileType type)
{
switch (type)
{
case TileType.Empty:
return ConsoleColor.Green;
case TileType.wall:
return ConsoleColor.Red;
default:
return ConsoleColor.Green;
}
}
}
}
Main 함수
더보기
namespace Algorithm
{
internal class Program
{
static void Main(string[] args)
{
Board board = new Board();
Player player = new Player();
board.Initialize(25, player);
player.Initialize(1, 1, board);
Console.CursorVisible = false;
const int WAIT_TICK = 1000 / 30;
int lastTick = 0;
while (true)
{
#region
// 만약에 경과한 시간이 1/30초보다 작다면
int currentTick = System.Environment.TickCount;
// 단위가 밀리세컨드 이기 때문에 1000을 곱해줌
if (currentTick - lastTick < WAIT_TICK)
continue;
int deltaTick = currentTick - lastTick;
lastTick = currentTick;
#endregion
// 입력
// 로직
player.Update(deltaTick);
// 렌더링
Console.SetCursorPosition(0, 0);
board.Render();
}
}
}
}
반응형
'C# > C# 일반' 카테고리의 다른 글
[C#] 제네릭의 비교 형식 제한 (0) | 2023.07.17 |
---|