Unity

[Unity] 2주차 틱택토 구현

박도치 2023. 11. 7. 21:30

2주차 과제에 text로 틱택토게임을 구현하라는 과제였는데 일반적인 배열로 판을 만드는게 아닌, 오늘 처음 배운 2차원 배열로 판을 만들고 이로 틱택토 게임을 하는것을 구현해 보겠다.

 

연습용으로 만드는 과제이기 때문에 잘 몰랐던 2차원 배열과 잘 못쓰던 do While을 사용하여 코드를 구현해 보았다.

 

1. 2차원 배열

2차원 배열은 행과 열로 이루어진 데이터 구조를 다루기에 적합한 배열의 한 종류다. 기본적인 코드는 아래와 같다.

 

int[,] map = new int[5, 5];
for (int i = 0; i < 5; i++)
{
    for (int j = 0; j < 5; j++)
    {
        map[i, j] = i + j;
    }
}

 

5, 5면 행이 5 열도 5칸인 표가 하나 만들어진다.

 

이를 이용해 3 x 3 틱택토 판을 만들어 구현하도록 하자.

 

2. 틱택토 구현

2-1. board와 플레이어 선언

플레이어는 X 를 놓는 플레이어와 O를 놓는 플레이어 둘 로 나뉜다. X 말을 가진 사람이 선으로 정한다.

 

보드는 3 X 3 보드이다.

static char[,] board = new char[3, 3];
static char currentPlayer = 'X';

// 보드 초기화
static void InitializeBoard()
{
    for (int row = 0; row < 3; row++)
    {
        for (int col = 0; col < 3; col++)
        {
            board[row, col] = ' ';
        }
    }
}

 

위는 선언 및 보드판 초기화 코드이다. 게임이 끝나고 나면 항상 초기 보드판으로 돌려놔야 하기 때문에, 반복을 두번 돌려 3 X 3으로 만들고, 값을 ' ' 빈칸으로 줘서 초기화를 시킨다.

 

// 보드판 생성
static void DisplayBoard()
{
    Console.Clear();

    // 좌표 써놓기
    Console.WriteLine("  0 1 2");
    for (int row = 0; row < 3; row++)
    {
        Console.Write(row + " ");
        for (int col = 0; col < 3; col++)
        {
        	// 이 좌표에 현재 currentPlayer의 좌표를 들고와서 찍어줌
            Console.Write(board[row, col]);
            if (col < 2)
            {
                Console.Write("|");
            }

        }
        Console.WriteLine();
        if (row < 2)
        {
            Console.WriteLine("  -+-+-");
        }
    }
}

 

보드판은 위와 같다. 사용자가 가로와 세로를 각각 입력하여 말을 두게 하는 방식인데, 가로 세로의 숫자가 헷갈릴 수 있기 때문에 가로 세로열에 맞춰 숫자로 좌표를 적어 놓기위해 가로 좌표를 먼저 쓴 후 반복문을 돌리는 식이다.

 

Console.Write(row + " "); 는 우측에 숫자를 쓰고 한칸 띄어서 숫자 옆에 "ㅣ" 표시가 들어갈 수 있도록 하기 위해 써넣었고, "ㅣ" 표시는 2개만 필요하기 때문에 if문으로 두 번만 출력했다. 

 

-+-+-+ 로 가운데 판을 십자모양으로 만들었으며 위를 출력하면 아래 사진과 같은 판이 완성된다.

 

 

 

2-2. 게임 실행

플레이어가 행과 열 순으로 좌표를 입력해야 하므로 먼저 가로 세로값을 선언한 후 플레이어가 입력할 수 있게 Console.ReadLine().Split(' ')으로 한칸 띄우고 입력할 수 있게 한 후 두 값을 각각 담아준다. 여기서 do내에 줘서 무조건 실행 및 무한반복을 걸어둔 후 while 에서 0, 1 ,2 가 아닌 다른 수를 쓰면 탈출 못하게 끔 코드를 짰다.

 

 //게임시 플레이어가 좌표 
 static void PlayerInputNum()
 {
     int row, col;
     do
     {
         Console.Write($"플레이어 {currentPlayer}, 행과 열을 입력하세요 (예: 0 0, 주의!: 0 부터 2까지만 입력해주세요): ");
         string[] input = Console.ReadLine().Split(' ');
         row = int.Parse(input[0]);
         col = int.Parse(input[1]);
     } while (row < 0 || row >= 3 || col < 0 || col >= 3 || board[row, col] != ' ');

     board[row, col] = currentPlayer;
 }

 

그리고 만약 입력한 수가 0, 1, 2 에 맞다면, currenPlayer(X 또는 O)가 어느 좌표에 썼는지 보내준다.

 

2-3. 플레이어 교대

// 플레이어 교대
static void SwitchPlayer()
{
    if (currentPlayer == 'X')
    {
        currentPlayer = 'O';
    }
    else
    {
        currentPlayer = 'X';
    }
}

 

현재 플레이어가 X 면 O로 아니면 X로 바꾸는 식으로 플레이어를 교대해주는 코드이다. 그래야 X한번 O한번 찍힌다.

 

2-3. 승리 및 패배

// 승자가 생기거나, 무승부시 게임 종료
static bool IsGameOver()
{
    return IsWinner() || IsBoardFull();
}
static bool IsWinner()
{
    for (int i = 0; i < board.GetLength(0); i++)
    {
        //가로 또는 세로 승리
        if (board[i, 0] == currentPlayer && board[i, 1] == currentPlayer && board[i, 2] == currentPlayer)
            return true;
        if (board[0, i] == currentPlayer && board[1, i] == currentPlayer && board[2, i] == currentPlayer)
            return true;
    }

    // 대각선 승리
    if (board[0, 0] == currentPlayer && board[1, 1] == currentPlayer && board[2, 2] == currentPlayer)
        return true;
    if (board[0, 2] == currentPlayer && board[1, 1] == currentPlayer && board[2, 0] == currentPlayer)
        return true;

    return false;
}

 

return이 true면 게임이 종료되게끔 코드를 짰다. 위 if문은 가로 또는 세로에 한 플레이어가 다 먹었을 시 true를 반환하고, 아래 if문은 대각선을 다 먹었을 시에 true를 반환해준다. 해당하지 않으면 false를 반환하여 isGameOver함수에 전달한다.

 

// 무승부
static bool IsBoardFull()
{
    for (int row = 0; row < 3; row++)
    {
        for (int col = 0; col < 3; col++)
        {
            if (board[row, col] == ' ')
                return false;
        }
    }
    return true;
}

위는 무승부 함수이다. board[row, col]에 빈칸이 있다면 false고 아니라면 true다. 즉, 꽉 차면 무승부이기 때문에 판이 꽉 찼을때 false를 반환하는 코드이다.

 

3. 마무리 및 전체코드

이런식으로 간단한 방식으로 틱택토를 만들어봤다. 아래는 전체 코드이고, 게임 승리시 화면이다.

 

namespace _2_Homework2
{
    internal class Program
    {
        static char[,] board = new char[3, 3];
        static char currentPlayer = 'X';

        static void Main(string[] args)
        {
            InitializeBoard();

            while (true)
            {
                DisplayBoard();
                PlayerInputNum();

                if (IsGameOver())
                {
                    DisplayBoard();
                    Console.WriteLine("게임 종료!");
                    break;
                }

                SwitchPlayer();
            }
        }

        // 보드 초기화
        static void InitializeBoard()
        {
            for (int row = 0; row < 3; row++)
            {
                for (int col = 0; col < 3; col++)
                {
                    board[row, col] = ' ';
                }
            }
        }

        // 보드판 생성
        static void DisplayBoard()
        {
            Console.Clear();

            // 좌표 써놓기
            Console.WriteLine("  0 1 2");
            for (int row = 0; row < 3; row++)
            {
                Console.Write(row + " ");
                for (int col = 0; col < 3; col++)
                {
                    Console.Write(board[row, col]);
                    if (col < 2)
                    {
                        Console.Write("|");
                    }
                        
                }
                Console.WriteLine();
                if (row < 2)
                {
                    Console.WriteLine("  -+-+-");
                }
            }
        }

        //게임시 플레이어가 좌표 
        static void PlayerInputNum()
        {
            int row, col;
            do
            {
                Console.Write($"플레이어 {currentPlayer}, 행과 열을 입력하세요 (예: 0 0, 주의!: 0 부터 2까지만 입력해주세요): ");
                string[] input = Console.ReadLine().Split(' ');
                row = int.Parse(input[0]);
                col = int.Parse(input[1]);
            } while (row < 0 || row >= 3 || col < 0 || col >= 3 || board[row, col] != ' ');

            board[row, col] = currentPlayer;
        }

        // 승자가 생기거나, 무승부시 게임 종료
        static bool IsGameOver()
        {
            return IsWinner() || IsBoardFull();
        }

        // 승자 함수
        static bool IsWinner()
        {
            for (int i = 0; i < board.GetLength(0); i++)
            {
                //가로 또는 세로 승리
                if (board[i, 0] == currentPlayer && board[i, 1] == currentPlayer && board[i, 2] == currentPlayer)
                    return true;
                if (board[0, i] == currentPlayer && board[1, i] == currentPlayer && board[2, i] == currentPlayer)
                    return true;
            }

            // 대각선 승리
            if (board[0, 0] == currentPlayer && board[1, 1] == currentPlayer && board[2, 2] == currentPlayer)
                return true;
            if (board[0, 2] == currentPlayer && board[1, 1] == currentPlayer && board[2, 0] == currentPlayer)
                return true;

            return false;
        }

        // 무승부
        static bool IsBoardFull()
        {
            for (int row = 0; row < 3; row++)
            {
                for (int col = 0; col < 3; col++)
                {
                    if (board[row, col] == ' ')
                        return false;
                }
            }
            return true;
        }

        // 플레이어 선공 교체
        static void SwitchPlayer()
        {
            if (currentPlayer == 'X')
            {
                currentPlayer = 'O';
            }
            else
            {
                currentPlayer = 'X';
            }
        }
    }
}

 

 

4. 마지막 한 마디

 

막상 만들고나니 누가 이겼는지, 다른 키를 눌렀을 때 경고 등 아쉬운 부분이 있었다. 사용자 입장에서 생각하며 코드를 짜는 연습을 많이 해야겠다고 느꼈고, 제대로된 설계와 함께 만들어야 역시 더 퀄리티있는 게임이 나올 것 같다.