사전캠프/달리기반

달리기반 LV.3 숫자 야구 게임

서보훈 2024. 8. 27. 16:27
  • 숫자 야구 게임을 작성하세요. 컴퓨터가 3자리의 숫자를 선택하면 사용자가 그 숫자를 맞추는 게임을 구현하세요. 각 자리의 숫자를 비교하여 스트라이크와 볼의 개수를 출력합니다.
    • 숫자 야구 게임 설명
    • 숫자 야구 게임은 3자리의 숫자를 맞추는 게임입니다. 컴퓨터가 고른 3자리 숫자는 모두 다른 숫자로 이루어져 있습니다. 사용자는 3자리 숫자를 입력하고, 컴퓨터는 입력한 숫자와 자리수를 비교하여 스트라이크와 볼의 개수를 알려줍니다.
    • 스트라이크: 숫자와 자리수가 모두 맞는 경우
    • : 숫자는 맞지만 자리수가 다른 경우
    예를 들어, 컴퓨터가 427을 선택하고 사용자가 123을 입력했을 때, 2는 맞지만 자리수가 다르므로 1볼, 1은 맞지 않으므로 0스트라이크입니다. 이 과정은 사용자가 정확한 숫자를 맞출 때까지 반복됩니다.

사용 변수 설명

  • targetNumber: 컴퓨터가 선택한 3자리의 숫자를 저장하는 배열입니다.
  • userGuess: 사용자가 추측한 숫자를 저장하는 배열입니다.
  • strikes: 자릿수와 숫자가 모두 맞는 경우의 개수를 저장합니다.
  • balls: 자릿수는 맞지 않지만 숫자가 포함된 경우의 개수를 저장합니다.
  • guessedCorrectly: 사용자가 숫자를 정확히 맞췄는지를 나타내는 불리언 변수입니다.

예상 출력

Enter your guess (3 digits): 123
0 Strike(s), 0 Ball(s)
Enter your guess (3 digits): 456
0 Strike(s), 0 Ball(s)
Enter your guess (3 digits): 789
0 Strike(s), 3 Ball(s)
Enter your guess (3 digits): 987
3 Strike(s), 0 Ball(s)
Congratulations! You've guessed the number in 4 attempts.

 


숫자 야구 게임 프로그램을 작성해야 합니다.

 

무작위로 3개의 숫자를 만들어낸 뒤, 3개의 숫자를 입력하여 스트라이크와 볼 을 돌려받아 3개의 숫자를 유추해내는 게임입니다.

 

게임의 흐름은 다음과 같습니다.

  1. 중복되지 않는 3개의 정답숫자 생성
  2. 유저로부터 3자리 숫자를 받음
  3. 입력한 숫자와 정답숫자의 자릿수와 숫자가 같다면 스트라이크, 입력한 숫자에 정답숫자가 있지만 위치가 다르면 볼
  4. 스트라이크와 볼의 갯수를 유저에게 알려줌
  5. 3스트라이크가 나올때 까지 반복

정답숫자를 만든 후 부터는 게임의 흐름이 반복되게 됩니다.

이를 기반으로 반복문의 흐름을 분석해보면

  1. 유저로부터 입력을 받음
  2. 입력이 3자리 숫자인지 판정
  3. 3자리 숫자일경우, 스트라이크와 볼 갯수 판정
  4. 3스트라이크일경우, 반복문을 종료
  5. 스트라이크,볼 갯수를 저장한 변수를 초기화
  6. 3자리 숫자가 아니거나, 숫자가 아닐경우 재입력 유도

이번 퀘스트에서 중요한점은 무작위로 생성하는 3개의 숫자는 중복되면 안된다는점 입니다.

 

코드를 작성해주도록 하겠습니다.

사용변수에 맞도록, 변수를 선언해줍니다.

//컴퓨터가 선택한 3자리 숫자 배열
int[] targetNumber = new int[3];
//사용자가 추측한 숫자를 저장할 배열
int[] userGuess = new int[3];
//자릿수와 숫자가 모두 맞는 경우의 개수 저장
int strikes = 0;
//자릿수는 맞지 않지만, 숫자가 포함된 경우의 개수 저장
int ball = 0;
//사용자가 숫자를 정확히 맞췄는지를 나타내는 변수
bool guessedCorrectly = false;
//시도 횟수
int attempts = 0;

결과물에 시도횟수가 표기되기 때문에, 시도횟수를 저장할 변수 또한 선언해주었습니다.

 

배열에 3자리 값을 지정하는 코드를 작성해주겠습니다.

//3자리 랜덤 숫자 배열 생성
//랜덤 클래스 객체 선언
Random random = new Random();

//배열에 3자리 값 지정
for (int i = 0; i < targetNumber.Length; i++)
{
    //0부터 10까지의 숫자 생성
    targetNumber[i] = random.Next(0, 10);
}

무작위 숫자를 생성해야하기 때문에, Random 클래스의 객체를 만들어주고 사용합니다.

숫자를 생성할때는 3자리 배열에 각각 숫자가 저장되어야하기 때문에, 0부터 9까지의 제한을 두어 한자리의 숫자가 3번 생성되어 각각 배열의 자리에 저장되도록 만들어줍니다.

또한, 숫자야구는 첫번째 자리에 0이 올 수도 있기 때문에 첫번째자리에 대한 조건은 사용하지 않겠습니다.

 

이 상태에서는 무작위 숫자가 생성되기 때문에, 중복되는 숫자가 생성되어 저장될 가능성이 있습니다.

중복된 숫자가 있으면 해당 자리의 숫자를 다시 생성하도록 만들어주겠습니다.

//배열에 3자리 값 지정
for (int i = 0; i < targetNumber.Length; i++)
{
    //0부터 10까지의 숫자 생성
    targetNumber[i] = random.Next(0, 10);
    //중복검사
    for (int j = 0; j < i; j++)
    {
        //첫번째 자리를 지정할때는 검사를 실행하지 않음
        if (targetNumber[j] == targetNumber[i])
        {
            //중복된 숫자가 있을경우, 현재 숫자를 다시 배정
            i--;
        }
    }
}

for문을 2중으로 만들어주었습니다.

첫번째 for문의 i 값이 0일때, targetNumber[0] 의 값이 정해지고 다음 열을 실행하려고 합니다.

이때는 조건식이 0 < 0 이 되어서 두번쨰 for문이 작동되지 않고, i = 1일때의 for문을 실행하게 됩니다.

 

targetNumber[1] 의 값이 정해지고, 두번째 for문에 접근합니다.

이때는 조건식이 0 < 1 이 되어서 두번째 for문이 작동하게 되고, 초기식이 j = 0 이기 때문에

targetNumber[0] 과 targetNumber[1] 에 저장된 값을 비교하게 됩니다.

 

두 배열에 저장된 값이 다르면 다음 반복을 진행할 수 없게되어 i = 2 일때의 첫번째 for문으로 넘어가지만, 두 값이 같아질경우 i값을 1 줄이게 되어 i = 1 인 상태로 첫번째 for문을 진행하게 됩니다.

 

이러면 다시 targetNumber[1] 에 접근하여 배열에 무작위 숫자를 저장하게 되고 두번째 for문을 진행하게 됩니다.

 

첫번째와 두번째 값이 중복되지 않은 값이 저장되어 i = 2 일때로 넘어가게 되면, 두번째 for문의 조건식이 j < 2 가 되어서 2번 반복하게 되고, targetNumber[2] 를 각각 targetNumber[0] 과 targetNumber[1] 에 비교하여 같은값이 있으면 숫자를 다시 지정하게 됩니다.

 

또한 0,1 번 배열에 중복되지 않는 값만이 저장되기 때문에, if문이 여러번 작동할 일이 없게되어 중복없는 3개의 값을 생성할 수 있게 됩니다.

 

주의할점은, 만약 생성가능한 숫자의 갯수보다 생성할 숫자의 갯수가 많아지게되면, 모든 숫자를 생성한 후 다음 숫자를 생성할때 중복검사를 통과할 수 없게되어 무한루프에 빠지게 됩니다.

 

중복되지 않는 3개의 숫자를 생성하였습니다.

이제 반복문을 통해 본격적임 게임을 진행하겠습니다.

//정답을맞출때까지 반복
while (!guessedCorrectly)
{
    //입력받기
    Console.Write("Enter your guess (3 digits) : ");
    //입력받은 문자열
    string input = Console.ReadLine();
    //정수형 변환시 받아줄 변수
    int inputInt;
    //입력값이 정수형이고, 3자리 일경우, 정답여부
    if (int.TryParse(input, out inputInt) && input.Length == 3)
    {
    
    }
    //입력값이 정수형이 아닐경우, 잘못된 입력임을 출력
    else
    {
        Console.WriteLine("Wrong input, enter 3digits");
    }
}

 

while문과 입력을 받아줄 부분을 만들어주었습니다.

while문은 true일때 반복하게 되는데, guessedCorrectly가 true일때 게임을 종료하는것이 직관적이기 때문에 NOT 연산자인 !를 붙여주었습니다.

 

또한 입력을 받을때,  3자리의 숫자만 입력받도록 하기 위해서 if문의 조건으로 int.TryParse AND input.Length  ==  3 을 사용하여 3자리의 숫자만 받아주도록 하였습니다

(1을 입력할경우, 문자열의 길이 조건이 없으면 001 으로써 처리하게되는데, 해당 부분이 게임의 직관성이 맞지 않다고 판단하였습니다)

 

입력한 숫자를 배열에 저장하도록 하겠습니다.

//정답을맞출때까지 반복
while (!guessedCorrectly)
{
    //...생략
    //입력값이 정수형이고,3자리일경우, 정답여부
    if (int.TryParse(input, out inputInt) && input.Length == 3)
    {
        for (int k = 0; k < input.Length; k++)
        {
            userGuess[k] = inputInt % 10;
            inputInt /= 10;
        }
    }
}

 

0번 자리에 1의 자리수, 1번 자리에 10의 자리수, 2번 자리에 100의 자리수의 정수를 저장하였습니다.

 

for문에 관한 설명입니다.

만약 123의 값이 들어오면 해당 값을 10으로 나누었을때의 나머지 3을 userGuess[0] 에 저장하고 들어온 123값을 10으로 나눈 몫인 12를 다시 inputInt에 저장합니다.

 

반복문이 완료되지 않았기 때문에, 다음 반복으로 넘어가서 현재 inputInt인 12를 10으로 나눈 나머지인 2를 userGuess[1] 에 저장하고, 몫인 1을 inputInt에 저장합니다.

 

이를통해 각각의 자리수를 배열에 저장할 수 있습니다.

 

스트라이크와 볼의 판정을 처리해주도록 하겠습니다.

//정답을맞출때까지 반복
while (!guessedCorrectly)
{
    //... 생략
    //입력값이 정수형이고, 3자리일경우, 정답여부
    if (int.TryParse(input, out inputInt) && input.Length == 3)
    {
        //...생략

        //스트라이크, 볼 판정
        for (int i = 0; i < targetNumber.Length; i++)
        {
            for (int j = 0; j < userGuess.Length; j++)
            {
                //같은 숫자 발견시
                if (targetNumber[i] == userGuess[j])
                {
                    //위치가 같을경우 스트라이크
                    if (i == j)
                    {
                        strikes++;
                    }
                    //위치가 다르면 볼
                    else
                    {
                        ball++;
                    }
                }
            }
        }
    }
}

2중 for문을 사용하여 스트라이크와 볼을 판정합니다.

첫번째 for문이 기준점이 되어, 두번째 for문의 각 자리수에 접근하여 같은 숫자가 있는지 확인합니다.

현재는 targetNumber 배열을 기준으로 userGuess 배열의 각 자리수와 비교하지만, 두 순서가 바뀌어도 결과의 차이는 없습니다.

 

if문 또한 2중으로 사용되었는데, 스트라이크와 볼 모두 같은 숫자가 있으면 작동하기때문에 첫번째 if문의 조건을 비교하는 숫자끼리 같을경우로 설정합니다.

 

targetNumber[0] 와 userGuess[0] 가 같을때 첫번째 if문이 작동하고, targetNumber[0] 와 userGusee[1] 이 같아도 첫번째 if문은 작동하지만 첫번째의 경우 스트라이크이고, 두번째의 경우 볼 입니다.

 

즉, 첫번째 if문이 true 일 때 각 자리수를 지정하는 i 와 j 가 같으면 스트라이크, 다르면 볼 이 됩니다.

따라서 두번째 if문의 조건을 i == j 로 설정하고 같으면 스트라이크 변수를 1 증가, else이면 볼의 변수를 1 증가시켜 줍니다.

 

스트라이크와 볼의 판정까지 끝났습니다.

판정이 끝난후, 후처리를 해주겠습니다.

//정답을맞출때까지 반복
while (!guessedCorrectly)
{
    //...생략
    if (int.TryParse(input, out inputInt) && input.Length == 3)
    {
        //...생략
        
        //판정 종료후, 스트라이크가 3이면 종료 준비
        if (strikes == 3)
        {
            guessedCorrectly = true;
        }
        //판정 결과 출력
        Console.WriteLine($"{strikes} Strike(s), {ball} Ball(s)");

        //스트라이크와 볼 초기화
        strikes = 0;
        ball = 0;
        //시도 1회 증가
        attempts++;
    }
}

strikes 변수가 3일경우, 세자리의 숫자를 모두 맞추었다는 뜻 입니다.

그럴경우 정답판정 변수인 guessedCorrectly 변수를 true로 변경하여 반복문을 종료할 준비를 해 줍니다.

 

판정이 종료된 후, 판정 결과를 알려주어야 합니다.

Console.WriteLine을 사용하여 입력한 숫자의 스트라이크와 볼이 몇개인지를 출력해주었습니다.

 

판정을 출력한 이후, 스트라이크와 볼 변수를 0으로 초기화 시켜주고, 시도횟수 변수를 1 늘려줍니다.

 

판정결과 출력과 반복문 종료 준비의 위치는 서로 바뀌어도 무관하지만 변수 초기화의 경우 반드시 결과 출력 후에 이루어져야 합니다.

 

마지막으로, while문 종료후 게임 결과를 출력해줍니다.

    //while문 종료후, 결과 출력
    Console.WriteLine($"Congratulations! You've guessed the number in {attempts} attempts.");

 


전체 코드입니다.

{
    //컴퓨터가 선택한 3자리 숫자 배열
    int[] targetNumber = new int[3];
    //사용자가 추측한 숫자를 저장할 배열
    int[] userGuess = new int[3];
    //자릿수와 숫자가 모두 맞는 경우의 개수 저장
    int strikes = 0;
    //자릿수는 맞지 않지만, 숫자가 포함된 경우의 개수 저장
    int ball = 0;
    //사용자가 숫자를 정확히 맞췄는지를 나타내는 변수
    bool guessedCorrectly = false;
    //시도 횟수
    int attempts = 0;

    //3자리 랜덤 숫자 배열 생성
    //랜덤 클래스 객체 선언
    Random random = new Random();

    //배열에 3자리 값 지정
    for (int i = 0; i < targetNumber.Length; i++)
    {
        //0부터 10까지의 숫자 생성
        targetNumber[i] = random.Next(0, 10);
        //중복검사
        for (int j = 0; j < i; j++)
        {
            //첫번째 자리를 지정할때는 검사를 실행하지 않음
            if (targetNumber[j] == targetNumber[i])
            {
                //중복된 숫자가 있을경우, 현재 숫자를 다시 배정
                i--;
            }
        }
    }

    for (int i = 0; i < targetNumber.Length; i++)
    {
        Console.WriteLine(targetNumber[i]);
    }

    //정답을맞출때까지 반복
    while (!guessedCorrectly)
    {
        //입력받기
        Console.Write("Enter your guess (3 digits) : ");
        string input = Console.ReadLine();
        int inputInt;
        //입력값이 정수형이고, 3자리일경우, 정답여부
        if (int.TryParse(input, out inputInt) && input.Length == 3)
        {
            //userGuess 배열에, 각 자리수 저장
            for(int k = 0; k < input.Length; k ++)
            {
                userGuess[k] = inputInt % 10;
                inputInt /= 10;
            }

            //스트라이크, 볼 판정
            for (int i = 0; i < targetNumber.Length; i++)
            {
                for (int j = 0; j < userGuess.Length; j++)
                {
                    //같은 숫자 발견시
                    if (targetNumber[i] == userGuess[j])
                    {
                        //위치가 같을경우 스트라이크
                        if (i == j)
                        {
                            strikes++;
                        }
                        //위치가 다르면 볼
                        else
                        {
                            ball++;
                        }
                    }
                }
            }
            //판정 종료후, 스트라이크가 3이면 종료 준비
            if (strikes == 3)
            {
                guessedCorrectly = true;
            }
            //판정 결과 출력
            Console.WriteLine($"{strikes} Strike(s), {ball} Ball(s)");

            //스트라이크와 볼 초기화
            strikes = 0;
            ball = 0;
            //시도 1회 증가
            attempts++;
        }

        //입력값이 정수형이 아닐경우, 잘못된 입력임을 출력
        else
        {
            Console.WriteLine("Wrong input, enter 3digits");
        }
    }

    //while문 종료후, 결과 출력
    Console.WriteLine($"Congratulations! You've guessed the number in {attempts} attempts.");
}

 


숫자의 각 자리수를 배열에 저장할경우, while문을 사용할 수 도 있습니다.

int whileNum = 0;

while (inputInt > 0)
{
    userGuess[whileNum] = inputInt % 10;
    inputInt /= 10;
    whileNum++;
}

 

해당 방법의 경우, 입력해준 숫자가 0이 될때 까지 반복문을 반복하여 자리수 만큼 배열에 값을 저장할 수 있습니다.

단, 이번 숫자야구 게임의 경우 첫번째 자리에 0이 들어올 수 있는데, 이 방법을 사용하게 될 경우 100의 자리수가 저장되어야할 userGuess[2] 번 값이 초기화되지 않아서 인식을 못하게 됩니다.

 

100의 자리에 무작위 생성으로 0 이 들어오게 될 경우 최대 스트라이크가 2까지만 나오게 되어 클리어가 불가능해지는 문제가 발생합니다.

따라서 이 방법을 사용할경우 userGuess의 각 자리를 0으로 초기화 해줄 필요가 있습니다.