Notice
Recent Posts
Recent Comments
Link
«   2026/05   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
Archives
Today
Total
관리 메뉴

I'm FanJae.

[C#] 콘솔 지뢰찾기 만들기 본문

Unity/Unity 초격차캠프

[C#] 콘솔 지뢰찾기 만들기

FanJae 2026. 4. 24. 22:15

1. 구현 방법

1. menu에서 입력을 받아서 입력에 따라 처리를 진행한다.
2. 게임 보드판을 출력하기 전에 게임 보드판의 '사전 방문 여부'를 모두 초기화한다.
3. 사용자에게 키보드로 부터 입력을 받아서 분기 처리를 진행한다.
[분기]
      1) 방향키(화살표)를 입력 받은 경우, 커서 이동을 진행한다.
      2) F키(깃발)버튼을 누른 경우, 방문하지 않은 칸에 깃발을 심게 한다.
      3) H키를 누른 경우, 도움말을 출력한다.
      4) Q키를 누른 경우, 게임을 종료한다.
      5) 스페이스 버튼을 누른 경우, 칸을 오픈한다.
4.  칸을 오픈 할 때, 다음과 같은 분기 처리를 진행한다.
     1) 첫번째 오픈을 할 때, 지뢰 셋팅을 진행한다.
     ※ 지뢰 찾기 게임은 첫 번째 오픈에서 절대 지뢰를 밟지 않기 때문이다.
     2) 오픈한 칸이 지뢰면 게임을 종료한다.
     3) 오픈한 칸이 숫자(8방향 내 지뢰가 있는 경우), 해당 숫자만 오픈한다.
     4) 오픈한 칸이 빈칸인 경우, 8방향 탐색을 깊이 진행한다.
※ 이 방향 탐색은 Logic은 별도 서술한다.
5. 게임을 계속 진행하여, 전체 칸 중에서 남은 칸의 개수가 지뢰의 개수와 같으면 승리한다.

2. 지뢰찾기 탐색 로직

① 8방향 탐색(주변 지뢰 탐색)

이전, 빙고나 오목과 다르게 지뢰찾기는 반드시 8방향 탐색을 모두 진행해야 한다.

이유는 각자가 자신의 주변에 있는 지뢰의 개수를 모두 알고 있어야 하기 때문이다.

지뢰 찾기에서는 숫자 칸은 해당 칸을 둘러싼 인접 칸에 존재하는 지뢰 개수를 나타낸다.

따라서, 빙고나 오목과는 다르게 8방향을 모두 탐색해야 한다.

 

② 8방향 탐색(빈칸 확장)

사용자가 클릭한 부분이 ‘빨간색’ 부분이었다고 가정할 때, 함께 오픈되는 곳은 파란색이다.

이 부분을 보고 우리가 알 수 있는 것은 이것이다.

 

8방향 내 인근 빈칸이 존재하는 경우, 해당 빈칸을 기준으로 8방향을 추가로 오픈한다.

8방향 내 인근 숫자가 있던 경우, 해당 빈칸만 오픈한다.

 

3. 구현

※ C# Source Code

더보기

1. menu() - 메뉴 기능

static int Menu() // 메뉴 기능
{
    string system_message = "";
    string input;
    do
    {
        Console.Clear();
        Console.WriteLine("===================");
        Console.WriteLine("===================");
        Console.WriteLine("[지뢰찾기 게임]    ");
        Console.WriteLine("1. 게임 플레이     ");
        Console.WriteLine("2. How to Play     ");
        Console.WriteLine("3. Credits         ");
        Console.WriteLine("4. 프로그램 종료   ");
        Console.WriteLine("===================");
        Console.WriteLine("===================");
        Console.Write(system_message);
        Console.Write("원하는 숫자를 입력하세요 : ");
        input = Console.ReadLine();

        if(int.TryParse(input, out int choice) && choice >= 1 && choice <= 4) // TryParse는 변환 실패시 false를 반환.
        {
            return choice;
        }
        system_message = "올바른 숫자를 입력해주시길 바랍니다.\n";

    } while (true);

    return 0; // 현 Logic 상 절대 도달 할 수 없음.
}
  • 메뉴 기능으로, 지뢰 찾기 게임 플레이를 하기 위한 입력을 받는 창이다.
  • string 값으로 입력을 받았지만, 메뉴에서 반환할 때는 int로 처리하였다.
  • TryParse() 는 자료형을 변환할 때, 실패할 경우 false 로 반환해 주기 때문에 사용자가 넣는 이상한 값을 대부분 예외 처리 할 수 있다. (기존 C++ std::cin 으로 정수 입력 받을 때, 사용자가 문자를 넣으면 터지는 문제를 해결 가능한 것이다.)

2. IndexSafeCheck() - 인덱스 범위를 초과하는지 확인하는 함수

static Boolean IndexSafeCheck(int cursorX, int cursorY, int boardSize) // 인덱스 범위를 넘어가는지 확인
{
    if(cursorX < 0 || cursorX >= boardSize)
    {
        return false;
    }
    if(cursorY < 0 || cursorY >= boardSize)
    {
        return false;
    }
    return true;
}
  • 게임 보드판(지뢰판)을 이동할 때, 인덱스 범위를 넘었는지 확인하는 함수이다.
  • 보드판의 범위를 넘은 경우 false 를 보드판의 범위를 넘지 않은 경우 true 를 반환한다.

 

3. InItBoard() - 보드 초기화

static void InItBoard(int[,] gameBoard) // 보드 초기화
{
    int n = gameBoard.GetLength(0); // 보드판이 n * n 형태
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < n; j++)
        {
            gameBoard[i, j] = -2;
        }
    }
}
  • 게임을 시작하기 전에, 보드를 열람하지 않은 상태로 셋팅하는 함수다.

 

4. PrintBoard() - 보드 출력하는 함수

static void PrintBoard(int[,] realBoard, int[,] gameBoard, int cursorX, int cursorY) // 보드 출력
{
    int n = gameBoard.GetLength(0); // 보드판이 n * n 형태

    Console.Clear();
    Console.WriteLine("[지뢰찾기 판]");
    Console.WriteLine("==========================");
    for(int i=0; i<n; i++)
    {
        for(int j=0; j<n; j++)
        {
            if (cursorX == i && cursorY == j) // 현재 커서 위치 출력
            {
                Console.Write("▣");
            }
            else if (gameBoard[i, j] == -2) // 열람하지 않은 경우
            {
                Console.Write("■");
            }
            else if (gameBoard[i, j] >= 1 && gameBoard[i, j] <= 8) // 지뢰 개수 오픈된 경우
            {
                Console.Write($"{gameBoard[i, j]} ");
            }
            else if (gameBoard[i, j] == 0) // 빈칸
            {
                Console.Write("□");
            }
            else if (gameBoard[i, j] == -3) // 깃발 세운 경우
            {
                Console.Write($"{"F"} ");
            }
        }
        Console.WriteLine();
    }
    Console.WriteLine("==========================");
    Console.WriteLine("단축키 도움말 버튼 : H");
}
  • 지뢰 찾기 판을 출력하는 함수이다. 게임 보드 판에서 현재 커서의 위치, 열람 여부, 오픈된 칸 중 빈칸 또는 숫자가 적혀있는 칸, 깃발을 출력한다.

 

 

5. MineBoardSetting() - 지뢰 찾기 게임판을 세팅하는 함수

static void MineBoardSetting(int[,] realBoard, int mineCount, int cursorX, int cursorY) // 지뢰 찾기 게임판 셋팅
{

    Random rand = new Random(); // 랜덤 값 생성 (지뢰가 배정될 위치)
    int mine_number;
    int n = realBoard.GetLength(0); // 보드판이 n * n 형태
    for (int i=1; i<=mineCount; i++)
    {
        bool mineSet = false;
        while(mineSet == false)
        {
            mine_number = rand.Next(n * n);
            if (realBoard[mine_number / n, mine_number % n] == 0 && !((cursorX == mine_number / n) && (cursorY == mine_number % n)))
            {
                realBoard[mine_number / n, mine_number % n] = -1; // 지뢰
                mineSet = true;
            }
        }
    }

    for (int i=0; i<n; i++)
    {
        for(int j=0; j<n; j++)
        {
            int cnt = 0;
            if (realBoard[i, j] == -1) continue; // 지뢰인 곳은 보드판 갱신하지 못하게 막음.
            for(int k=0; k<8; k++)
            {
                int x = i + direction[k,0];
                int y = j + direction[k,1];

                if(IndexSafeCheck(x,y,n) == true)
                {
                    if (realBoard[x,y] == -1)
                    {
                        cnt++;
                    }
                }
            }
            realBoard[i, j] = cnt; // 8방향 내 지뢰 개수 정보 업데이트
        }
    }

}
  • 지뢰 찾기 게임판을 셋팅한다.
  • 지뢰 찾기에서 항상 사용자가 첫 번째로 칸 열기를 시도할 때는 지뢰를 밟지 않음을 보장해야 하기 때문에, 사용자가 첫 번째로 칸 열기를 시도한 시점에서 지뢰 찾기 게임판을 셋팅하도록 처리하였다.

 

6. OpenBoard() - 칸 열기를 진행하는 함수

static Boolean OpenBoard(int[,] realBoard, int[,] gameBoard, int cursorX, int cursorY) // 칸 열기
{
    int target = realBoard[cursorX, cursorY]; // 해당 칸 확인

    if(target >=1 && target <= 8) // 8방향 내 지뢰 개수가 적힌 숫자면 해당 칸만 오픈
    {
        gameBoard[cursorX, cursorY] = realBoard[cursorX, cursorY];
    }
    else if(target == 0) // 빈칸이면, 주변 빈칸 및 숫자 개수 모두 탐색(8방향 내)
    {
        dfs(realBoard, gameBoard, cursorX, cursorY);
    }
    else if (target == -1) // 지뢰인 경우 게임 끝
    {
        return true;
    }
    return false;
}
  • 칸 열기를 진행한다. 8방향 내 지뢰 개수가 적힌 숫자 칸이면 해당 칸만 오픈한다.
  • 만약, ‘빈칸’인 경우에는 해당 칸을 기준으로 8방향을 또 오픈해야 하기 때문에, 이 경우에는 깊이 우선 탐색(DFS) 로직을 이용하여 탐색한다.
  • 지뢰인 경우 true를 반환하여 게임이 끝나며, 지뢰가 아닌 경우, false를 반환한다.

7. dfs() - 빈칸을 하나하나 탐색하는 함수

static void dfs(int[,] realBoard, int[,] gameBoard, int cursorX, int cursorY) // 지뢰 찾기에서 빈칸일때 하나하나 들어가서 탐색
{
    int n = realBoard.GetLength(0);
    gameBoard[cursorX, cursorY] = realBoard[cursorX, cursorY];
    for (int k = 0; k < 8; k++)
    {
        int x = cursorX + direction[k, 0];
        int y = cursorY + direction[k, 1];

        if (IndexSafeCheck(x, y, n) == true)
        {
            if (realBoard[x, y] == -1 && gameBoard[x, y] == -2) // 지뢰 무시
            {
                continue;
            }
            else if (realBoard[x, y] >= 0 && gameBoard[x, y] == -2) // 방문한 적이 없는 부분 확인.
            {
                gameBoard[x, y] = realBoard[x, y];
                if (realBoard[x, y] == 0) // 만약 그곳도 빈칸이면 더 깊숙히 탐색 들어감.
                {
                    dfs(realBoard, gameBoard, x, y);
                }
            }
        }
    }
}
  • 빈칸을 하나하나 탐색 해보는 함수로, 8방향 내 에서 지뢰는 무시하고, 방문한 적이 없는 곳을 확인한다.
  • 보드판에서는 0 이상의 값(숫자가 적혀있거나, 빈칸)인 경우 해당 칸을 탐색하도록 처리하였다.
  • 만약, 8방향 내 탐색한 그 칸도 빈칸(숫자 0)인 경우, 그 칸을 기준으로 탐색을 계속한다.

8. WinCheck() - 승리 여부를 확인하는 함수

static Boolean WinCheck(int[,] realBoard, int[,] gameBoard) // 승리 여부 확인
{
    int n = gameBoard.GetLength(0); // 보드판이 n * n 형태 보장.
    int none_mine_cnt = 0;
    int safe_cnt = 0;

    for(int i=0; i<n; i++)
    {
        for(int j=0; j<n; j++)
        {
            if (realBoard[i,j] != -1) // 실제 보드에서 지뢰가 아닌 개수를 확인
            {
                none_mine_cnt++;
            }
            if (gameBoard[i,j] >= 0) // 게임 보드에서 열람된 애들 중 안전한 애들 확인.
            {
                safe_cnt++;
            }
        }
    }

    if(none_mine_cnt == safe_cnt) // 지뢰가 아닌 곳의 개수와 열린 갯수가 같으면 승리
    {
        return true;
    }
    else
    {
        return false;
    }       
}
  • 지뢰 찾기 게임에서 승리 여부를 확인하는 함수이다.
  • 실제 보드에서 지뢰가 아닌 개수를 확인하고, 게임 보드에서는 열람된 애들 중 안전한 칸(지뢰가 아닌 칸)이 무엇인지 확인한다. (깃발이 놓인 칸은 열람된 칸의 개념으로 보지 않는다.)

9. PlayGame() - 게임 플레이 함수

static void PlayGame()
{
    int[,] gameBoard = new int[9, 9];
    int[,] realBoard = new int[9, 9];
    int board_size = gameBoard.GetLength(0); // 지뢰찾기의 보드 판은 n*n 형태.
    int mineCount = 10;
    int cursorX = 0;
    int cursorY = 0;

    string system_message = "";

    Boolean first_open = true;
    ConsoleKeyInfo input_key;

    InItBoard(gameBoard);
    while (true)
    {
        PrintBoard(realBoard, gameBoard, cursorX, cursorY);
        // DebugBoard(realBoard, board_size); 지뢰 찾기를 잘 못하여 답 출력 해놓고 테스트...


        Console.Write(system_message);
        input_key = Console.ReadKey(true);

        if(input_key.Key == ConsoleKey.UpArrow) // 위쪽 화살표
        {
            cursorX--;
            if (IndexSafeCheck(cursorX, cursorY, board_size) == false) cursorX++; // 초과 범위 처리
        }
        else if(input_key.Key == ConsoleKey.DownArrow) // 아래쪽 화살표
        {
            cursorX++;
            if (IndexSafeCheck(cursorX, cursorY, board_size) == false) cursorX--; // 초과 범위 처리
        }
        else if(input_key.Key == ConsoleKey.LeftArrow) // 왼쪽 화살표
        {
            cursorY--;
            if (IndexSafeCheck(cursorX, cursorY, board_size) == false) cursorY++; // 초과 범위 처리
        }
        else if (input_key.Key == ConsoleKey.RightArrow) // 오른쪽 화살표
        {
            cursorY++;
            if (IndexSafeCheck(cursorX, cursorY, board_size) == false) cursorY--; // 초과 범위 처리
        }
        else if (input_key.Key == ConsoleKey.Spacebar) // 칸 열기
        {
            if (first_open == true) // 사용자가 첫번째로 칸을 열었을때는 지뢰가 아님을 보장.
            {
                MineBoardSetting(realBoard, mineCount, cursorX, cursorY);
                first_open = false;
            }

            if (gameBoard[cursorX, cursorY] == -2) // 열람 된 적이 없어야 함.
            {
                if (OpenBoard(realBoard, gameBoard, cursorX, cursorY) == true)
                {
                    Console.WriteLine("사용자가 지뢰를 클릭하였습니다.");
                    Console.WriteLine("메뉴로 돌아가려면 아무키나 입력하세요");
                    Console.ReadKey();
                    break;
                }

                if(WinCheck(realBoard,gameBoard) == true) // 승리 체크했을때 이긴 경우 게임 종료.
                {
                    Console.WriteLine("축하합니다. 승리하셨습니다.");
                    Console.WriteLine("메뉴로 돌아가려면 아무키나 입력하세요");
                    Console.ReadKey();
                    break;
                }
            }
            else
            {
                system_message = "이미 열람된 칸을 다시 열 수 없습니다.";
                continue ;
            }
        }
        else if (input_key.Key == ConsoleKey.F) // 깃발 세우기
        {
            if (gameBoard[cursorX, cursorY] == -2) // 열람 된 적이 없어야 함.
            {
                gameBoard[cursorX, cursorY] = -3; // 깃발
            }
            else if (gameBoard[cursorX, cursorY] == -3)
            {
                gameBoard[cursorX, cursorY] = -2; 
            }
            else
            {
                system_message = "이미 열람된 곳에는 깃발을 세울 수 없습니다.";
                continue;
            }
        }
        else if (input_key.Key == ConsoleKey.Q) // 게임 종료
        {
            Console.WriteLine("사용자가 게임을 종료하였습니다.");
            Console.WriteLine("메뉴로 돌아가려면 아무키나 입력하세요");
            Console.ReadKey();
            break;
        }
        else if (input_key.Key == ConsoleKey.H) // 도움말
        {
            Help();
        }
        system_message = "";
    }
}
  • 전반적인 게임 플레이와 관련된 분기를 처리하는 함수이다.
  • 보드 사이즈는 고정이지만, 이후 난이도 분기(초급,중급,고급)등을 고려하여 난이도 확장을 할 수 있는 여지를 남겨두었다.
input_key = Console.ReadKey(true);

if(input_key.Key == ConsoleKey.UpArrow) // 위쪽 화살표
{
    cursorX--;
    if (IndexSafeCheck(cursorX, cursorY, board_size) == false) cursorX++; // 초과 범위 처리
}
else if(input_key.Key == ConsoleKey.DownArrow) // 아래쪽 화살표
{
    cursorX++;
    if (IndexSafeCheck(cursorX, cursorY, board_size) == false) cursorX--; // 초과 범위 처리
}
else if(input_key.Key == ConsoleKey.LeftArrow) // 왼쪽 화살표
{
    cursorY--;
    if (IndexSafeCheck(cursorX, cursorY, board_size) == false) cursorY++; // 초과 범위 처리
}
else if (input_key.Key == ConsoleKey.RightArrow) // 오른쪽 화살표
{
    cursorY++;
    if (IndexSafeCheck(cursorX, cursorY, board_size) == false) cursorY--; // 초과 범위 처리
}
else if (input_key.Key == ConsoleKey.Spacebar) // 칸 열기
{
    if (first_open == true) // 사용자가 첫번째로 칸을 열었을때는 지뢰가 아님을 보장.
    {
        MineBoardSetting(realBoard, mineCount, cursorX, cursorY);
        first_open = false;
    }

    if (gameBoard[cursorX, cursorY] == -2) // 열람 된 적이 없어야 함.
    {
        if (OpenBoard(realBoard, gameBoard, cursorX, cursorY) == true)
        {
            Console.WriteLine("사용자가 지뢰를 클릭하였습니다.");
            Console.WriteLine("메뉴로 돌아가려면 아무키나 입력하세요");
            Console.ReadKey();
            break;
        }

        if(WinCheck(realBoard,gameBoard) == true) // 승리 체크했을때 이긴 경우 게임 종료.
        {
            Console.WriteLine("축하합니다. 승리하셨습니다.");
            Console.WriteLine("메뉴로 돌아가려면 아무키나 입력하세요");
            Console.ReadKey();
            break;
        }
    }
    else
    {
        system_message = "이미 열람된 칸을 다시 열 수 없습니다.";
        continue ;
    }
}
else if (input_key.Key == ConsoleKey.F) // 깃발 세우기
{
    if (gameBoard[cursorX, cursorY] == -2) // 열람 된 적이 없어야 함.
    {
        gameBoard[cursorX, cursorY] = -3; // 깃발
    }
    else if (gameBoard[cursorX, cursorY] == -3)
    {
        gameBoard[cursorX, cursorY] = -2; 
    }
    else
    {
        system_message = "이미 열람된 곳에는 깃발을 세울 수 없습니다.";
        continue;
    }
}
else if (input_key.Key == ConsoleKey.Q) // 게임 종료
{
    Console.WriteLine("사용자가 게임을 종료하였습니다.");
    Console.WriteLine("메뉴로 돌아가려면 아무키나 입력하세요");
    Console.ReadKey();
    break;
}
else if (input_key.Key == ConsoleKey.H) // 도움말
{
    Help();
}
  • C++ 에서 _getch() 를 활용한 입력과 동시에 키 값을 받아온 것처럼, 여기서는 Console.ReadKey() 를 이용하여 입력과 동시에 키 값을 받아오게 처리하였다.
  • 이를 이용해서 커서 이동시 화면에 즉시 출력되도록 처리하였고, 입력 즉시 반영될 수 있게 처리를 진행하였다.

 

4. 실행 결과

 
 
Comments