본문 바로가기

TIL

[TIL]2024-1-2 / 7일차 - 어려웠던 틱택토 게임 만들기

오늘은 2번 강의를 교육하며 C# 기초와 2번 과제를 해결하기로 했다.

3번 강의까지 듣고 싶었지만 과제에서 좀 헤매는 바람에 시간이 부족하였다.

 

처음엔 강의 교육 내용을 정리하고자 했지만 과제 내용이 많아 그것을 쓰고자 한다.

 

과제는 숫자 맞추기 게임과 틱택토 만들기 였는데 숫자 맞추기는 강의 내용을 참고하며 금방 작성할 수 있었기에 코드만 남겨 놓았다.

강의 과제 - 숫자 맞추기 게임

더보기

숫자 맞추기 게임 - 1부터 100 사이 숫자를 맞춰나가는 게임

ReadLine()을 받은 숫자가 랜덤 넘버보다 큰지 작은지 비교하여 출력해주는 것만 생각하면 됬다.

static void Main(string[] args)
{
    //숫자 맞추기
    int targetNumber = new Random().Next(1, 101);
    int guess = 0;
    int count = 0;
    Console.WriteLine("1부터 100 사이의 숫자를 맞춰보세요");

    while(guess != targetNumber)
    {
        Console.Write("추측한 숫자를 입력하세요: ");
        guess = int.Parse(Console.ReadLine());
        count++;

        if(guess < targetNumber)
        {
            Console.WriteLine("좀 더 큰 숫자를 입력하세요.");
        }
        else if(guess > targetNumber)
        {
            Console.WriteLine("좀 더 작은 숫자를 입력하세요.");
        }
        else
        {
            Console.WriteLine("축하합니다! 숫자를 맞추셨습니다!");
            Console.WriteLine("시도한 횟수 : " + count);
        }
    }
}

 

강의 과제 - 틱택토 만들기

틱택토 만들기는 어려운 점들이 있었다

 

1. 승리 조건을 체크하는 조건문 작성 - 가로, 세로, 대각선 승리 조건 / 무승부 출력

2. 9칸을 배치하고 배열로 저장한 뒤 이를 ReadLine() 방식 input으로 칸을 X 혹은 O로 바꾸는 방법

3. 플레이어의 턴을 배정하는 방법

 

이 세 개의 방법을 정하는데 고민을 많이 했다.

 

우선 2번의 9칸 배치는 본 2번 강의에서 설명해준 2차원 배열을 사용해보기로 했다.

static char[,] numberField = new char[3, 3]
    {
        {'1', '2', '3' },
        {'4', '5', '6' },
        {'7', '8', '9' }
    };

 

이렇게 배열을 저장한 뒤 //다음에는 numberField처럼 긴 이름 말고 더 짧게 써보자... 나중에 길어져서 보기 불편했다.

 

static void Field()
{
    Console.WriteLine("플레이어 1: X 와 플레이어 2: O ");
    Console.WriteLine();
    Console.WriteLine("-------------");
    Console.WriteLine("| {0} | {1} | {2} |", numberField[0, 0], numberField[0, 1], numberField[0, 2]);
    Console.WriteLine("I---I---I---I");
    Console.WriteLine("I {0} | {1} | {2} |", numberField[1, 0], numberField[1, 1], numberField[1, 2]);
    Console.WriteLine("I---I---I---I");
    Console.WriteLine("| {0} | {1} | {2} |", numberField[2, 0], numberField[2, 1], numberField[2, 2]);
    Console.WriteLine("-------------");
    Console.WriteLine();
}

 

이렇게 필드를 WriteLine()을 통해 나타냈다.

 

위 코드의 구동화면

 

그럼 이제는 어떻게 플레이어의 턴을 체크할지 고민해야했다.

그 방법으로 처음에는 Turn이라는 함수를 만들어 체크해보고자 했지만 더 좋은 방법이 떠올랐다.

 

후에 무승부를 체크하는 방법으로 복잡한 코드를 생각하기 보다

틱택토라는 게임의 특성상 턴의 횟수가 9번 이상을 넘을 수 없게 되어있는데

이를 체크하기 위해 턴 Count를 제기로 하였다.

int count = 1; 를 만들고 플레이어가 칸을 선택한 뒤 count++;를 통해 턴을 하나씩 늘려주고 이렇게 생겨난 턴 숫자를

 

2명의 플레이어 뿐이기에 

if (count % 2 == 0)
{
    Console.WriteLine("플레이어 2의 차례");
}
else
{
    Console.WriteLine("플레이어 1의 차례");
}

홀수 짝수를 확인하던 코드를 이용해 count가 짝수라면 플레이어 2, 홀수라면 1로 나타내도록 했다.

 

이제 문제는 입력 받은 칸을 'O'와 'X'로 바꾸는 방법이었다.

이를 위해 우선 이해해야 했던 것이

 

2차원 배열에서 각 칸은 16진법을 통한다는 것이었다.

그렇기에 Input으로 적은 숫자로 각 해당하는 칸을 호출하기 위해서는

Input/3으로 나눈 값 x%3을 한 나머지 값 ynumberField[x, y]로 나타내야 했다.

 

이렇게 호출된 칸을 플레이어의 턴을 체크하여 'X' 혹은 'O'로 바꿔줘야 했다. 이 코드는

int row = userInput / 3;
int column = userInput % 3; //코드 단순화를 위한 함수
                
if (count % 2 == 0)
{
    numberField[row, column] = 'O';
}
else
{
    numberField[row, column] = 'X';
}
count++; //턴을 바꿈

 

그리고 또 한 가지 신경 써야 할 것은 이미 'O' 혹은 'X'로 점유된 칸을 다시 호출할 때로 이때는 바뀌거나 count가 높아져서는 안된다 그렇기에 이렇게 칸을 변경하는 코드 이전에 이미 변경된 칸인지 체크하는 코드를 작성하여 먼저 체크하도록 했다.

int row = userInput / 3;
int column = userInput % 3;

if (numberField[row, column] == 'X' || numberField[row, column] == 'O') //코드 추가
{
	continue;
}

if (count % 2 == 0)
{
	numberField[row, column] = 'O';
}
else
{
	numberField[row, column] = 'X';
}
count++;

 

이렇게 해서 어려웠던 파트 중 하나를 해결했다.

 

이제 플레이가 가능해졌으니 해야 하는 것은 승리 조건을 구현해서 게임이 끝나도록 하는 것

 

이에는 여러가지 방식이 있었고 어떻게 구현해야 할지 어려움을 느껴 주변인에게 도움을 요청했고 복잡한 방법보다 보다 직관적인 방법으로 input 받은 칸을 인식한 뒤 그 주변의 칸을 체크해서 3칸이 채워졌는지 확인하는 코드를 작성하라는 조언을 받았다.

 

좋은 방법이라 생각하여 마침 2차원 배열을 이용했기에 각 row와 column 값을 고정하고 나머지 값을 확인하면 되어 조금 더 짧은 코드를 작성할 수 있을 거라고 생각했다.

승리하는 플레이어의 경우 count % 2는 똑같이 하되 이는 1, 0 값으로 나오기 때문에 + 1을 해주었다.

 

이를 고려해 다음과 같이 구현했다.

if (numberField[row, 0] == numberField[row, 1] && numberField[row, 1] == numberField[row, 2])
{
    Console.Clear();
    Console.WriteLine("플레이어 {0}의 승리입니다!", (count % 2) + 1);
    break;
}
else if (numberField[0, column] == numberField[1, column] && numberField[1, column] == numberField[2, column])
{
    Console.Clear();
    Console.WriteLine("플레이어 {0}의 승리입니다!", (count % 2) + 1);
    break;
}

(numberField가 너무 긴 이름이었다...)

 

이렇게 각각 가로 승리 조건 / 세로 승리 조건을 체크할 수 있게 되었다.

하지만 틱택토는 대각선을 채워 승리할 수도 있다.

그렇지만 대각선을 만드는 방법은 단 두 개 뿐으로 1~9 번호 중 1 / 5 / 9와 3 / 5 / 7 을 채워 3개를 만드는 방법 뿐이다.

이를 더 세련되게 체크할 수도 있겠지만 이미 여기까지 공부하는데 피로를 느껴 대각선은 단순하게 체크하기로 했다.

else if (numberField[0, 0] == numberField[1, 1] && numberField[1,1] == numberField[2, 2] || numberField[0, 2] == numberField[1, 1] && numberField[1, 1] == numberField[2, 0])
{
    Console.Clear();
    Console.WriteLine("플레이어 {0}의 승리입니다!", (count % 2) + 1);
    break;
}

"작동하니 아무튼 된 것 아닐까?"

 

그리고 아까 말한 무승부 조건은 턴 수를 통해 체크했다. 이 체크를 할 때 칸의 변경 보다 먼저 체크하게 될 경우 칸 변경이 안되고 게임이 끝나는 어색함이 나타나기에 무승부 체크를 순서 상 모든 변경이 끝난 가장 마지막에 체크하도록 하여 해결하였다.

if (count > 9)
{
    Console.Clear();
    Console.WriteLine("무승부 입니다!");
    Console.WriteLine();
    break;
}

 

이렇게 해서 요구 조건대로 작동하는 틱택토가 완성되었다.

잘 작동하는 승리 모습

 

완성된 코드는 접은글에 남겨 놓도록 하겠다.

더보기

틱택토 게임 전체 코드

static char[,] numberField = new char[3, 3]
    {
        {'1', '2', '3' },
        {'4', '5', '6' },
        {'7', '8', '9' }
    };
static void Field()
{
    Console.WriteLine("플레이어 1: X 와 플레이어 2: O ");
    Console.WriteLine();
    Console.WriteLine("-------------");
    Console.WriteLine("| {0} | {1} | {2} |", numberField[0, 0], numberField[0, 1], numberField[0, 2]);
    Console.WriteLine("I---I---I---I");
    Console.WriteLine("I {0} | {1} | {2} |", numberField[1, 0], numberField[1, 1], numberField[1, 2]);
    Console.WriteLine("I---I---I---I");
    Console.WriteLine("| {0} | {1} | {2} |", numberField[2, 0], numberField[2, 1], numberField[2, 2]);
    Console.WriteLine("-------------");
    Console.WriteLine();
}
static void Main(string[] args)
{
    int userInput;
    int count = 1;
    
    while (true)
    {
        Console.Clear();
        Field();

        if (count % 2 == 0)
        {
            Console.WriteLine("플레이어 2의 차례");
        }
        else
        {
            Console.WriteLine("플레이어 1의 차례");
        }
        userInput = int.Parse(Console.ReadLine()) - 1;
        int row = userInput / 3;
        int column = userInput % 3;

        if (numberField[row, column] == 'X' || numberField[row, column] == 'O')
        {
            continue;
        }

        if (count % 2 == 0)
        {
            numberField[row, column] = 'O';
        }
        else
        {
            numberField[row, column] = 'X';
        }
        count++;

        if (numberField[row, 0] == numberField[row, 1] && numberField[row, 1] == numberField[row, 2])
        {
            Console.Clear();
            Console.WriteLine("플레이어 {0}의 승리입니다!", (count % 2) + 1);
            break;
        }
        else if (numberField[0, column] == numberField[1, column] && numberField[1, column] == numberField[2, column])
        {
            Console.Clear();
            Console.WriteLine("플레이어 {0}의 승리입니다!", (count % 2) + 1);
            break;
        }
        else if (numberField[0, 0] == numberField[1, 1] && numberField[1,1] == numberField[2, 2] || numberField[0, 2] == numberField[1, 1] && numberField[1, 1] == numberField[2, 0])
        {
            Console.Clear();
            Console.WriteLine("플레이어 {0}의 승리입니다!", (count % 2) + 1);
            break;
        }

        if (count > 9)
        {
            Console.Clear();
            Console.WriteLine("무승부 입니다!");
            Console.WriteLine();
            break;
        }
    }
    Field();
}

오늘은 어렵지만 무언가 배운 건 많았다. 하지만 3주차에 더 어려운 과제가 나오기 때문에 이 속도로 하게 된다면

진짜 과제인 개인 과제를 금요일까지 제출하는데 어려움이 클 것 같다.

 

3주차 과제는 도움을 많이 받아서라도 좀 더 빠르게 해결하고 4주차 강의를 내일까지 들을 수 있도록 해야 하겠다.