상세 컨텐츠

본문 제목

0906 TIL - C# 기초 문법 5

본문

오늘은 마지막으로 숫자 야구게임을 만드는 것으로 기초 문법을 마무리 하려고 한다.

 

아니 클래스랑 상속이나 라이브러리 같은건 하나도 안다루셨는데요?

다음에 다뤄보도록 하자.

 

숫자야구게임은 랜덤한 3자리의 숫자를 맞추는 게임으로써

3자리 숫자는 모두 다른 자리의 숫자로 이루어져있다.

 

랜덤한 숫자를 맞추면 볼

위치까지 맞추면 스트라이크로

3스트라이크를 통해 정답을 맞추는 게임이다.

 

우선 랜덤으로 3자릿수의 값을 출력해보자

중요한 건 3자릿수 중에 같은 숫자는 있으면 안된다는 것이다.

 

우선 대충 이러면 되려나 싶게 출력해보자.

using System;

Random random = new Random();
char[] targetNumber = StrikeZone();

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

char[] StrikeZone()
{
    char[] a = new char[3];
    for(int i=0; i < a.Length; i++)
    {
        a[i] = Convert.ToChar(random.Next(1, 10));

        Console.WriteLine(a[i]);
    }
    return a;
}

대충 코드를 만들고 실행해보면?

 

아무 결과도 안나온다.

 

아마도 int형과 char형의 길이에 따른 문제인것 같다.

char는 2byte이고 int는 4byte이니 int16으로 컨버트 한 후 다시 char로 컨버트해보자.

 

안된다.

 

뭐가 문제인지 살펴보자.

2byte라면 16비트이고 16비트라면 65535개의 문자를 표현할 수 있다.

 

문자 1을 int형으로 출력해보면 49가 나온다.

이게 무슨소리냐면 ASCII코드를 참조해보자.

옛날 1바이트의 크기로 문자를 표시하기 위해 만들어진 ASCII코드이다.

표를 자세히 살펴보면 0~9까지는 문자가 아닌 특수 기호들로 이루어져있다.

 

48번부터 57번까지를 살펴보면 0~9까지의 숫자가 들어있는 것을 볼 수 있다.

즉 우리는 여태

Null, SOH, STX, ETX, EOT, ENQ, ACK, BEL, BS, TAB을 출력중이였던 것이다...

 

이제 범위를 알아냈으니 다시 시도해보자.

정상적으로 작동하는 걸 볼 수 있다.

 

그럼 입력받을때도 48~57을 입력해야하나요??

그렇게 입력 받을 수도 있지만 입력받은 문자열을 문자로 나눈다면 그에 상관없이 그 값을 가지고 있다.

 

이전 시간에 문자열을 입력받아서 문자로 변환했던 함수를 가져와보자.

그때는 한 글자를 입력받아서 문자로 변환하기 편했지만 문자열을 문자 배열로 바꾸는 데에는 다른 함수가 필요하다.

ToCharArray()라는 함수를 사용할 건데 간단하게 문자열을 문자 배열로 변환해준다.

 

string input = Console.ReadLine(); //문자열로 입력을 받아온다.
char[] chars = input.ToCharArray(); //입력 받아온 문자열을 문자 배열로 바꿔준다.

for(int i = 0; i < chars.Length; i++)
{
    Console.WriteLine(chars[i]); //길이만큼 출력
}

 

155를 받아왔지만 49 53 53으로 표시되지 않고 1 5 5 로 표시되는 것을 확인 할 수 있다.

 

이제 StrikeZone 함수를 손봐서 혹시 중복 된다면 랜덤 생성하게 만들어보자.

char[] StrikeZone()
{
    char[] a = new char[3];
    for(int i=0; i < a.Length; i++)
    {
        a[i] = Convert.ToChar(random.Next(48, 58));
        for(int j = 0; j < a.Length; j++)
        {
            if (a[i] == a[j])
            {
                a[i] = Convert.ToChar(random.Next(48, 58));
            }
        }
    }
    return a;

이 코드를 작성하면서 a[i =0] == a[j = 0]같이 i와 j가 동수인 경우 리소스 낭비가 생기는거 같다.

 

혹시라도 최적화가 필요하다면

char[] StrikeZone()
{
    char[] a = new char[3];
    for(int i=0; i < a.Length; i++)
    {
        a[i] = Convert.ToChar(random.Next(48, 58));
        for(int j = 0; j < a.Length; j++)
        {
            if (i == j)
            {
                continue; //아래 구문을 실행하지 않음
                if (a[i] == a[j])
                {
                    a[i] = Convert.ToChar(random.Next(48, 58));
                }
            }
            
        }
    }
    return a;
}

i와 j가 같은 값이라면 비교하지않고 넘어가게 continue문을 통해 해결할 수 있다.

 

정상 출력되는 것을 확인 했으니 입력받은 숫자와 비교하여 strike와 ball을 구현해보자.

 

그 전에 혹시라도 문자나 4자릿수 이상의 숫자를 입력할 사람들이 있을까봐

입력하는 부분을 함수로 만들어 검토하도록 하자.

char[] Input(string a)
{
    char[] x = a.ToCharArray();
    if (x.Length != 3) //입력받은 글자가 3자릿 수 이상이라면
    {
        Console.WriteLine("3자리의 숫자를 입력해주세요.");
        return Input(Console.ReadLine()); //다시 입력하게 만든다.
    }
    for (int i = 0; i < a.Length; i++)
    {
        if (x[i] > 57 || x[i] < 48) //ASCII코드상 숫자가 아니라면
        {
            Console.WriteLine("3자리의 숫자를 입력해주세요.");
            return Input(Console.ReadLine()); //다시 입력하게 만든다.
        }
    }
    return x;
}

 

자 이제 편안해졌으니 ball과 strike를 구현해보자.

 

우선 게임은 while을 통해 승리조건이 false인 동안 계속되게 만들자.

bool win = false;

while (win == false)
{

}

Strike나 Ball을 맞췄다면

_ Strike(s)! _ Ball(s)!를 표시되게 해보자.

매 시작마다 Strike와 Ball을 0으로 초기화하여 일정 숫자 이상으로 올라가는 것을 막자.

 

그리고 정확히 해당 위치에 맞췄다면 Strike에 1씩 추가 못맞췄다면 Ball에 1씩 추가해보자.

한 칸마다 모든 입력한 숫자를 비교해야 하니 이중 for문으로 작성해주자

 

using System;

Random random = new Random();
char[] targetNumber = StrikeZone(); //스트라이크존 함수로 랜덤한 3개의 숫자를 생성
char[] guessNumber = Input(Console.ReadLine()); //Input함수를 통해 검열 후 입력받음
int chance = 0; //승리 시 몇 번 만에 맞췄는지 표시용
bool win = false; //승리조건

while (win == false) //승리조건이 만족되지 않았다면 반복
{
    int strike = 0; 
    int ball = 0; //매 반복마다 strike와 ball을초기화

    for(int i = 0; i < targetNumber.Length; i++)
    {
        for (int j = 0; j < guessNumber.Length; j++)
        {
            if (targetNumber[i] == guessNumber[i]) //위치까지 맞췄다면 스트라이크
            {
                strike++;
            }
            else if (targetNumber[i] == guessNumber[j]) //위치만 못 맞췄다면 볼
            {
                ball++;
            }
        }
    }
    Console.WriteLine(strike + "Strike(s), " + ball + " Ball(s)"); //스트라이크와 볼 갯수 출력
    chance++; //실행횟수에 1을 더함
}

.

거의 다 되었다 마지막으로 3 strike라면 게임을 종료하게 만들고 실행해보자.

using System;

Random random = new Random();
char[] targetNumber = StrikeZone(); //스트라이크존 함수로 랜덤한 3개의 숫자를 생성
char[] guessNumber = Input(Console.ReadLine()); //Input함수를 통해 검열 후 입력받음
int chance = 0; //승리 시 몇 번 만에 맞췄는지 표시용
bool win = false; //승리조건

while (win == false) //승리조건이 만족되지 않았다면 반복
{
    int strike = 0;
    int ball = 0; //매 반복마다 strike와 ball을초기화

    for (int i = 0; i < targetNumber.Length; i++)
    {
        for (int j = 0; j < guessNumber.Length; j++)
        {
            if (targetNumber[i] == guessNumber[i]) //위치까지 맞췄다면 스트라이크
            {
                strike++;
            }
            else if (targetNumber[i] == guessNumber[j]) //위치만 못 맞췄다면 볼
            {
                ball++;
            }
        }
    }

    if (strike == 3) 스트라이크가 3이라면
    {
        Console.WriteLine("축하합니다. 모든 숫자를 맞추셨습니다!");
        win = true;
    }

    Console.WriteLine(strike + "Strike(s), " + ball + " Ball(s)"); //스트라이크와 볼 갯수 출력
    chance++; //실행횟수에 1을 더함
}

실행해보자.

무한 루프에 걸려버렸다.

 

생각해보니 못 맞추면 재입력을 넣어두지 않았다.

재입력을 추가하는 김에 정답이라면 strike와 ball 갯수를 적지 않아도 될 것 같다.

 

while (win == false) //승리조건이 만족되지 않았다면 반복
{
    int strike = 0;
    int ball = 0; //매 반복마다 strike와 ball을초기화

    for (int i = 0; i < targetNumber.Length; i++)
    {
        for (int j = 0; j < guessNumber.Length; j++)
        {
            if (targetNumber[i] == guessNumber[i]) //위치까지 맞췄다면 스트라이크
            {
                strike++;
            }
            else if (targetNumber[i] == guessNumber[j]) //위치만 못 맞췄다면 볼
            {
                ball++;
            }
        }
    }

    if (strike == 3)
    {
        Console.WriteLine("축하합니다." + chance + "회 만에 모든 숫자를 맞추셨습니다!");
        win = true;
    }
    else
    {
        Console.WriteLine(strike + "Strike(s), " + ball + " Ball(s)"); //스트라이크와 볼 갯수 출력
        chance++; //실행횟수에 1을 더함
        guessNumber = Input(Console.ReadLine());
    }
}

if와 else로 분리해주자.

 

실행해보자

 

...다시 실행해보자.

 

????????

스트라이크가 3을 넘어버렸다.

아마도 i와 j 두 반복문 사이에서 9번 실행되니 두 숫자를 맞추면 6번의 스트라이크가 생성되는 것 같다.

즉 한 자리만 맞추면 승리하는데 그렇지 않다면 6 또는 9 스트라이크가 나오는 것 같다.

 

if와 else if 반복문을 이중 반복문에서 꺼내주자.

 

같은 수의 비교는 한 번만 비교하면 되기 때문에 첫번째 for문에만 적용시키고

2번째 for문에는 ball을 확인 할 수 있게 바꿔주자.

for (int i = 0; i < targetNumber.Length; i++)
{
    if (targetNumber[i] == guessNumber[i]) //위치까지 맞췄다면 스트라이크
    {
        strike++;
    }
    else
    {
        for (int j = 0; j < guessNumber.Length; j++)
        {
        if (targetNumber[i] == guessNumber[j]) //위치만 못 맞췄다면 볼
            {
                ball++;
            }
        }
    }
}

실행해보면?

뭔가 이상하다 2스트라이크 1볼이 가능한 숫자인가?

 

정답표를 불러와서 뭐가 문제인지 다시 테스트해보자.

아닌가 정상 작동하는 건가

뭔가 이상하다.

 

여러번 반복 실행해보니

중복된 숫자가 나오는 경우가 발생했다.

아마 위에서 리소스 낭비될것 같다고 예외 처리한 부분에서 발생하는 오류인것 같다.

 

해당 부분은 지워주자...

 

이렇게 야구 게임을 완성해보았다.

 

코드전문

using System;

Random random = new Random();
char[] targetNumber = StrikeZone();
char[] guessNumber = Input(Console.ReadLine());
int chance = 1;
bool win = false;


while (win == false)
{
    int strike = 0;
    int ball = 0;

    for (int i = 0; i < targetNumber.Length; i++)
    {
        if (targetNumber[i] == guessNumber[i])
        {
            strike++;
        }
        else
        {
            for (int j = 0; j < guessNumber.Length; j++)
            {
            if (targetNumber[i] == guessNumber[j])
                {
                    ball++;
                }
            }
        }
    }

    if (strike == 3)
    {
        Console.WriteLine("축하합니다." + chance + "회 만에 모든 숫자를 맞추셨습니다!");
        win = true;
    }
    else
    {
        Console.WriteLine(strike + "Strike(s), " + ball + " Ball(s)");
        chance++; //실행횟수에 1을 더함
        guessNumber = Input(Console.ReadLine());
    }
}

char[] StrikeZone()
{
    char[] a = new char[3];
    for(int i = 0; i < a.Length; i++)
    {
        a[i] = Convert.ToChar(random.Next(48, 58));
        for(int j = 0; j < a.Length; j++)
        {
            if (a[i] == a[j])
            {
                a[i] = Convert.ToChar(random.Next(48, 58));
            }
        }
    }
    return a;
}

char[] Input(string a)
{
    char[] x = a.ToCharArray();
    if (x.Length != 3)
    {
        Console.WriteLine("3자리의 숫자를 입력해주세요.");
        return Input(Console.ReadLine());
    }
    for (int i = 0; i < x.Length; i++)
    {
        if (x[i] > 57 || x[i] < 48)
        {
            Console.WriteLine("3자리의 숫자를 입력해주세요.");
            return Input(Console.ReadLine());
        }
    }
    return x;
}

관련글 더보기