전체 코드

namespace Algorithm
{
    class Program
    {
        static void Main(string[] args)
        {

            // Board 클래스의 인스턴스를 생성하고 맵을 초기화.
            Board board = new Board();
            Player player = new Player();   
            board.Initialize(25,player); // 맵의 크기를 25x25로 설정.
            Console.CursorVisible = false; // 콘솔의 커서를 숨김. 깔끔한 화면 출력.
            player.Initialize(1, 1,board.Size-2, board.Size-2, board);
            const int MAX_TICK = 1000 / 30; // 초당 30프레임을 유지하기 위한 최대 틱. (1초 / 30프레임)

            int lastTick = 0; // 마지막 프레임이 실행된 시간을 저장.

            // 메인 게임 루프
            while (true)
            {
                #region 프레임 관리
                // 프레임 시간 관리 (FPS 관리)
                // 현재 시간을 가져와서 지난 프레임 시간과 비교하여, 일정 시간이 지나지 않았다면 다음 루프를 건너뜀.
                int currentTick = System.Environment.TickCount; // 현재 시간을 밀리초 단위로 가져옴.
                int elapsedTick = currentTick - lastTick; // 지난 프레임 이후 경과 시간 계산.

                // 만약 경과 시간이 설정한 프레임 간격(MAX_TICK)보다 작다면 루프를 건너뜀.
                if (elapsedTick < MAX_TICK)
                {
                    continue; // 일정 시간이 지나지 않았으므로 이번 루프는 건너뜀.
                }
                int deltaTick = currentTick - lastTick; // 1프레임 지날 때마다 업데이트

                lastTick = currentTick; // 마지막 프레임 시간을 현재 시간으로 갱신.

                #endregion

                // 입력 처리 부분 (현재는 구현되어 있지 않음).
                // 사용자 입력: 키보드, 마우스 등.

                // 게임 로직 처리 부분 (현재는 구현되어 있지 않음).
                // AI 및 게임 내 로직 처리.
                player.Update(deltaTick);


                // 렌더링: 화면에 그려주는 단계.
                Console.SetCursorPosition(0, 0); // 콘솔 출력 위치를 맨 위로 이동하여 화면을 새로 그리도록 함.
                board.Render(); // 맵을 그리는 메서드 호출
            }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Algorithm
{
    class Board
    {
        public enum TileType
        {
            Empty, // 빈 공간
            Wall,  // 벽
        }
        const char CIRCLE = '\u25cf'; // 유니코드 문자로 ● (검은색 원)을 출력하기 위한 상수. 맵 타일을 표시하는 데 사용됨.
        public TileType[,] Tile { get; private set; } // 2차원 배열로 맵의 각 타일의 상태를 저장.
        public int Size { get; private set; } // 맵의 크기. NxN 크기를 나타냄.
                          // 맵 초기화 메서드. 맵의 크기를 받아 타일을 생성하고 초기화.


        Player _player;
        public void Initialize(int size, Player player)
        {


            if (size % 2 == 0)
            {
                return;
            }


            _player = player;

            Tile = new TileType[size, size]; // 맵의 크기(size x size)만큼의 2차원 배열 생성.
            Size = size; // 맵 크기 설정.

            //GenrateByBinaryTree();
            GenerateBySideWinder();

        }
        public void GenrateByBinaryTree()
        {
            // mazes for Programmers
            // Binary Tree Algorithm
            // 길을 다 막는 작업
            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;
                    }
                }
            }

            // 랜덤으로 우측 혹은 아래로 길을 뚫는 작업
            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;
                    }
                }
            }
        }

        // Sidewinder 알고리즘으로 미로를 생성하는 메서드
        public void GenerateBySideWinder()
        {
            // 1. 우선 모든 타일을 벽으로 막고, 홀수 좌표에 빈 공간을 만듦
            for (int y = 0; y < Size; y++)
            {
                for (int x = 0; x < Size; x++)
                {
                    // x 또는 y가 짝수일 경우에는 벽을 설치하고, 나머지에는 빈 공간을 생성
                    if (x % 2 == 0 || y % 2 == 0)
                    {
                        Tile[y, x] = TileType.Wall;  // 벽 타일
                    }
                    else
                    {
                        Tile[y, x] = TileType.Empty; // 빈 공간
                    }
                }
            }

            // 2. 각 좌표에서 무작위로 오른쪽 또는 아래쪽으로 길을 뚫음
            Random rand = new Random();  // 무작위 생성기

            for (int y = 0; y < Size; y++)
            {
                for (int x = 0; x < Size; x++)
                {
                    // 연속된 세로 구역의 길 개수를 세기 위한 변수 (미완성 코드로 보임)
                    int count = 0;

                    // 짝수 좌표는 벽이므로 건너뜀
                    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;
                    }

                    // 50% 확률로 오른쪽 또는 아래쪽으로 길을 뚫음
                    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; // 카운트 초기화
                    }
                }
            }
        }

        // 맵을 화면에 출력하는 메서드.
        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;
                    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; // 기본값으로 초록색 반환.
            }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Algorithm
{
    class Player
    {
        // 설계적으로 어떻게 진행해야 할까?
        public int PosY { get; private set; }
        public int PosX { get; private set; }

        Random _random = new Random();

        Board _board;

        public void Initialize(int posY, int posX, int destX, int destY, Board board)
        {
            PosX = posX;
            PosY = posY;

            _board = board;
        }

        const int MOVE_TICK = 100;   // 0.1 초 마다 움직이게
        int _sumTick = 0;

        // 상하좌우 움직임
        public void Update(int deltaTick)
        {
            _sumTick += deltaTick;
            if (_sumTick >= MOVE_TICK)
            {
                _sumTick = 0;

                // 여기에다가 0.1초마다 실행될 로직을 넣어 준다. 
                int randValue = _random.Next(0, 4);  // 4 가지의 랜덤한 방향
                switch (randValue)
                {
                    case 0:   // 상
                        if (PosY - 1 >= 0 && _board.Tile[PosY - 1, PosX] == Board.TileType.Empty)
                            PosY = PosY - 1;
                        break;
                    case 1:   // 하
                        if (PosY + 1 < _board.Size && _board.Tile[PosY + 1, PosX] == Board.TileType.Empty)
                            PosY = PosY + 1;
                        break;
                    case 2:   // 좌
                        if (PosX - 1 >= 0 && _board.Tile[PosY, PosX - 1] == Board.TileType.Empty)
                            PosX = PosX - 1;
                        break;
                    case 3:   // 우
                        if (PosX + 1 < _board.Size && _board.Tile[PosY, PosX + 1] == Board.TileType.Empty)
                            PosX = PosX + 1;
                        break;
                }
            }
        }
    }
}

Player 클래스 (플레이어 이동 구현)

class Player
{
    public int PosY { get; private set; }
    public int PosX { get; private set; }
    
    Random _random = new Random();
    Board _board;

    const int MOVE_TICK = 100;   // 0.1초마다 이동
    int _sumTick = 0;

    public void Initialize(int posY, int posX, Board board)
    {
        PosX = posX;
        PosY = posY;
        _board = board;
    }

    public void Update(int deltaTick)
    {
        _sumTick += deltaTick;
        if (_sumTick >= MOVE_TICK)
        {
            _sumTick = 0;

            int randValue = _random.Next(0, 4);  // 4가지 방향 선택
            switch (randValue)
            {
                case 0: // 상
                    if (PosY - 1 >= 0 && _board.Tile[PosY - 1, PosX] == Board.TileType.Empty)
                        PosY -= 1;
                    break;
                case 1: // 하
                    if (PosY + 1 < _board.Size && _board.Tile[PosY + 1, PosX] == Board.TileType.Empty)
                        PosY += 1;
                    break;
                case 2: // 좌
                    if (PosX - 1 >= 0 && _board.Tile[PosY, PosX - 1] == Board.TileType.Empty)
                        PosX -= 1;
                    break;
                case 3: // 우
                    if (PosX + 1 < _board.Size && _board.Tile[PosY, PosX + 1] == Board.TileType.Empty)
                        PosX += 1;
                    break;
            }
        }
    }
}

설명

  • PosX, PosY: 플레이어 위치를 나타냄.
  • MOVE_TICK = 100: 0.1초마다 이동하도록 설정.
  • Update(int deltaTick): 일정 시간이 지나면 4방향 중 하나를 랜덤 선택해 이동.
  • Initialize(int posY, int posX, Board board): 초기 위치 및 보드 정보를 저장.

Board 클래스 (미로 보드 구현)

class Board
{
    const char CIRCLE = '\u25cf'; // '●' 문자 사용
    public TileType[,] Tile { get; private set; }
    public int Size { 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;

        GenerateBySideWinder();
    }

    public void Render()
    {
        ConsoleColor prevColor = Console.ForegroundColor;
        for (int y = 0; y < Size; y++)
        {
            for (int x = 0; x < Size; x++)
            {
                if (y == _player.PosY && x == _player.PosX)
                    Console.ForegroundColor = ConsoleColor.Blue;
                else
                    Console.ForegroundColor = GetTileColor(Tile[y, x]);
                
                Console.Write(CIRCLE);
            }
            Console.WriteLine();
        }
        Console.ForegroundColor = prevColor;
    }
}

설명

  • TileType[,] Tile: 미로 데이터를 저장하는 2차원 배열.
  • Initialize(int size, Player player): 보드 크기를 초기화하고 미로 생성 알고리즘 실행.
  • Render(): 콘솔 화면에 미로와 플레이어를 출력.

Program 클래스 (메인 게임 루프 구현)

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 MAX_TICK = 1000 / 30;
        int lastTick = 0;

        while (true)
        {
            int currentTick = Environment.TickCount;
            int deltaTick = currentTick - lastTick;
            if (deltaTick < MAX_TICK) continue;
            lastTick = currentTick;

            player.Update(deltaTick);
            Console.SetCursorPosition(0, 0);
            board.Render();
        }
    }
}

설명

  • board.Initialize(25, player): 미로 크기 25×25로 설정.
  • player.Initialize(1, 1, board): 플레이어 시작 위치 설정.
  • MAX_TICK = 1000 / 30: 30 프레임을 유지하도록 설정.
  • while (true): 무한 루프로 프레임 관리.
  • deltaTick을 계산해 이동 로직 실행 후 Render() 호출.

프레임 관리 원리

const int MAX_TICK = 1000 / 30; // 30 FPS 유지
int lastTick = 0;
while (true)
{
    int currentTick = Environment.TickCount;
    int deltaTick = currentTick - lastTick;
    if (deltaTick < MAX_TICK) continue;
    lastTick = currentTick;
}

설명

  • Environment.TickCount: 현재 시간을 밀리초 단위로 반환.
  • MAX_TICK = 1000 / 30: 한 프레임당 약 33ms (30FPS) 유지.
  • 만약 경과 시간이 MAX_TICK보다 작으면 루프를 건너뛰어 프레임 속도를 조절.

profile
李家네_공부방

0개의 댓글