[C#] Practice Test (Console)

Lingtea_luv·2025년 3월 22일
0

C#

목록 보기
9/37
post-thumbnail

숫자 야구 게임


다음 조건을 만족하는 숫자 야구 게임을 구현하시오.

1. 컴퓨터는 1~9 중에 랜덤한 4자리 숫자를 뽑는다. 단, 중복은 허용하지 않는다.
2. 유저는 10번의 기회가 있다.
3. 플레이어가 수를 입력하면 컴퓨터는 아래조건에 맞추어 결과를 알려준다.
	A. Ball : 자리수는 다르지만 포함된 경우
	B. Strike : 자리수와 값이 동일한 경우
	C. Out : 숫자가 하나도 맞지 않을 경우
	D. HomeRun : 모든 숫자가 자리수와 값이 동일한 경우
4. 10번의 기회 소진 전까지 정답을 맞추면 승리하며, 모든 기회를 소진하면 패배한다.

1. pseudo 코드

static void Main(string[] args)
{
    bool gameOver = false;    // 게임 종료 조건 설정

    Start();
    // 시작 조건. 4자리 난수 (디버깅 Console.WriteLine() 활용)
    
    while(gameOver == false)
    {
        Render();  // 입력값에 따라 Ball, Strike, Out, HomeRun 표시
        Input();   // 사용자 입력값 
        Update();  // strike, ball 로직 구현 -> Render()와 합치는 건?
    }

    End();         // 게임 종료 페이즈.
}

static void Start()
{
	// 1~9까지 숫자 중에 4개를 중복없이 뽑도록
    // Random과 Next() 활용.
    // 중복을 제외하기 위해서는? -> 뽑은 숫자 제외? (알고리즘 고민할 것)
}

static void Render+Update()
{
	// 숫자, 자릿값 비교를 통해 Strike, Ball, Out, HomeRun 로직 구현 및 출력
}

static void Input()
{
	// 사용자 입력값 형변환 string -> int[]
}


static void End()
{
	// 10번째 시도에서 맞추는 경우, 못 맞추는 경우 나누어서 멘트 출력
}

2. Start( )

Random number = new Random();
array = new int[4];

for(int i = 0; i< array.Length; i++)
{
    array[i] = number.Next(1, 10);
}

위의 코드의 경우 array에 숫자가 중복될 수 있기에, 이를 기본 골자로 중복된 숫자를 제거하기 위한 알고리즘을 추가할 필요성이 있다. 따라서 유한 수열을 무작위 순열로 섞는 알고리즘인 Fisher-Yates shuffle을 활용했는데, 이미 뽑힌 숫자를 배열의 맨 앞으로 빼서 선택지에서 빼버리도록 하는 것이다.

피셔-예이츠 셔플(Fisher-Yates shuffle)

Random number = new Random();
int[] arrayGen = new int[9];
array = new int[4];

for (int i = 0; i < arrayGen.Length; i++)
{
    arrayGen[i] = i + 1;
}
for (int i = 0; i < arrayGen.Length; i++)
{
    int a = number.Next(i + 1, 10);
    int temp = arrayGen[a - 1];
    arrayGen[a - 1] = arrayGen[i];
    arrayGen[i] = temp;
}
for (int i = 0; i < 4; i++)
{
    array[i] = arrayGen[i];
}

먼저 1~9까지 나열되어있는 배열을 만들고 이해를 돕기 위해 인덱스의 시작을 1이라고 가정하자.
arrayGen = {1,2,3,4,5,6,7,8,9}
첫 번째 랜덤 숫자 6을 뽑은 경우 6과 1의 위치를 바꾼다.
arrayGen = {6,2,3,4,5,1,7,8,9}
두 번째 랜덤 숫자 5를 뽑은 경우 5와 2의 위치를 바꾼다.
arrayGen = {6,5,3,4,2,1,7,8,9}

이렇게 랜덤 숫자를 뽑을 때마다 뽑은 횟수에 해당하는 인덱스 값과 서로 위치를 바꾸는 것이 Fisher-Yates shuffle 알고리즘이다. 이때 위 배열의 변화를 잘 보면 눈치챘겠지만, 6을 뽑고 위치를 바꿨음에도, 두 숫자를 제외한 나머지 숫자와 그에 해당하는 인덱스 값은 동일하다는 것을 알 수 있다. 따라서 Next(i+1,10) 의 기능이 이미 뽑은 것을 제외하고 남은 배열의 요소 중에 하나를 선택하도록 되는 것이다.
여기서 arrayGen의 앞에서부터 4번째의 요소를 선택하는 구문이 더해지면 비록 Fisher-Yates shuffle이 유한한 숫자를 무작위로 나열하는 알고리즘이지만, 이를 활용하여 중복을 제외한 랜덤 숫자 추출 기능이 완성되는 것이다.

3. Input( )

Console.WriteLine($"{count++}번째 기회");
Console.Write("4자리 숫자를 입력해주세요 : ");
int input = int.Parse(Console.ReadLine());
List<int> inputList = new List<int>();
while (input != 0)
{
    inputList.Add(input % 10);
    input /= 10;
}
inputList.Reverse();
return inputList.ToArray();

여기서 해결해야하는 문제는 stringint array로 변환하는 것이다. 입력 값으로 6248라는 숫자를 받는다면 6,2,4,8의 각 자리 숫자를 비교해야하기 때문이다.

string[] input = Console.ReadLine().Split("");
int[] inputArray = new int[4];
for (int i = 0; i < input.Length; i++)
{
    inputArray[i] = int.Parse(input[i]);
    Console.Write(inputArray[i]);
}

일단 먼저 생각한 방법은 입력값을 string 문자열로 받아 "" 을 기준으로 나누려한 것이다. 하지만 이 경우 "" 기준으로 문자열을 나누는 것이 불가능하여 오류가 생겼다. 오류의 원인을 간단히 해석해보자면 컴퓨터가 입력받을 때 문자들이 붙어있으면 이를 하나의 덩어리로 처리하도록 묶고, 공백" "을 기준으로 덩어리를 나누기 때문에 ""를 인식하지 못하는 것이다. 마찬가지로 숫자를 입력해도 공백 기준으로 묶어 하나의 숫자로 인식해버리기 때문에 나누는 것이 불가능하다.

따라서 이 문제를 해결하기 위해 먼저 입력값을 int 로 바꾸고 for 문을 통해 자릿값에 해당하는 숫자를 반복해서 List<int> 에 추가하도록 구현하였다.

int input = int.Parse(Console.ReadLine());
List<int> inputList = new List<int>();
while(input != 0)
{
    inputList.Add(input % 10);
    input /= 10; 
}
inputList.Reverse();
return inputList.ToArray();

물론 이렇게 List에 하나씩 요소를 추가했으므로 마지막에 순서를 거꾸로 뒤집기 위해 .Reverse() 와 배열로 형변환을 하기 위해 .ToArray() 를 사용했다.

4. Render + Update( )

  • Update()
int strike = 0;
int ball = 0;
for (int i = 0; i < 4; i++)
{
    if (array[i] == inputArray[i])
    {
        strike++;
    }
    else
    {
        for (int j = 0; j < array.Length; j++)
        {
            if (array[i] == inputArray[j])
            {
                ball++;
            }
        }
    }
}
  • Render()
if (strike == 4)
{
    Console.WriteLine("Home Run!");
}
else if (strike == 0 && ball == 0)
{
    Console.WriteLine("Out!");
}
else
{
    Console.WriteLine($"Strike : {strike} Ball : {ball}");
}
  • IsCorrect()
public static bool IsCorrect()
{
    return array.SequenceEqual(inputArray) || count == 11 ? true : false;
}

시도를 할 때마다 조건에 부합하도록 출력하는 것은 어렵지 않았으나, 이후 End 페이지로 넘어가면서 10번째 시도에서는 결과에 따라 다른 멘트를 출력하는 것이 문제가 되었다. 이를 위해 우선 IsCorrect() 에서 10번째 시도 이후에는 무조건 게임이 종료되도록 설정했다.

5. End( )

public static void End()
{
    if (array.SequenceEqual(inputArray))
    {
        Console.WriteLine("승리했습니다!");
    }
    else
    {
        Console.WriteLine("기회 소진! GameOver~");
    }
}

여기서 해결할 문제는 10번째 시도에서 맞췄을 경우 HomeRun이 뜨면서 클리어 멘트가 출력되고, 못 맞췄을 경우 최대 시도 횟수 초과로 종료 멘트가 출력되도록 하는 것이었다. 이를 위해 10번째 시도에는 무조건 gameOver값이 true가 되도록 설정하고, End 페이지에서 배열 일치 여부에 따라 클리어 멘트와 실패 멘트가 출력되도록 구현하였다.

6. 추가 과제

1번 조건에서 0을 제외한 1~9의 숫자 중에 랜덤으로 뽑는다는 조건을 보고 왜 0이 제외되었을까에 대한 의문이 들어, 0을 넣고 테스트를 해본 결과 배열 맨 앞 자리에 0이 생성된 경우 비교 과정에서 index 범위로 오류가 걸리는 것을 확인할 수 있었다. 이는 int.Parse 형변환 과정에서 맨 앞 자리에 0이 있으면 이를 무시하고 저장하기 때문이다. 예를들어 "0123" 을 int.Parse를 통해 변환하면 123 이 되는 것이다. 그렇다면 이를 해결하기 위해서는 입력값을 숫자로 형변환하는 과정에서 데이터 손실 없이 변환이 가능하도록 해야할 것이다.

Q. 0을 포함한 0~9의 숫자로 야구게임을 하기 위해서는 어떻게 해야하는가?

A. 입력값을 string에서 바로 int로 바꾸지 않고 먼저 char array로 변환하고 이를 int array로 변환하면 데이터 손실이 없지 않을까?

profile
뚠뚠뚠뚠

0개의 댓글