TIL#73-Unreal C++ 개인 프로젝트 (2) : 숫자 야구 판정 로직 및 입력 유효성 검사

ㅎ12·4일 전
post-thumbnail

🔖 [Unreal Engine 5.5 / C++] - CH4 개인 프로젝트 (1) : 숫자 야구 판정 로직 및 입력 유효성 검사 (~20261012 pm12)

📚 금일 학습 목표

: 실제 숫자 야구 게임을 진행하기 위해 필요한 정답 생성, 입력 유효성 검사, Strike / Ball / Out 판정 로직을 중심으로 정리했다.

특히 플레이어가 입력한 숫자를 서버에서 검사하고, 정상적인 입력에 대해서만 시도 횟수를 증가시키는 과정이 중요했다.

  • 1~9 사이의 중복되지 않는 3자리 정답 생성
  • 문자열을 활용한 입력 유효성 검사
  • Strike / Ball / Out 판정 조건 구현
  • 플레이어별 최대 3회 시도 횟수 관리
  • 잘못된 입력에 대한 예외 처리 및 테스트

1. 숫자 야구 정답 생성

1.1 정답 생성 조건

숫자 야구 게임에서는 서버가 정답 숫자를 생성하고, 두 플레이어가 같은 정답을 기준으로 게임을 진행해야 한다.

이번 프로젝트의 정답 생성 조건은 다음과 같다.

  • 1~9 사이의 숫자만 사용한다.
  • 서로 다른 숫자 3개를 선택한다.
  • 숫자의 위치에 따라 판정 결과가 달라진다.
  • 게임을 초기화할 때 새로운 정답을 생성한다.

ex) 386, 527은 정상적인 정답이지만, 112는 숫자가 중복되고 305는 0이 포함되어 있으므로 사용할 수 없다.

1.2 TArray와 난수 생성

중복되지 않는 정답을 생성하기 위해서는 먼저 1~9 사이의 숫자를 준비하고, 그중 3개를 무작위로 선택하는 과정이 필요하다.

언리얼 엔진에서는 TArray를 활용하여 여러 숫자를 저장할 수 있고, FMath::RandRange()를 통해 특정 범위의 난수를 생성할 수 있다.

TArray<int32> Numbers;

for (int32 i = 1; i <= 9; ++i)
{
    Numbers.Add(i);
}

위 코드는 1부터 9까지의 숫자를 배열에 저장하는 과정이다.

  • TArray<int32> : 정수형 데이터를 여러 개 저장하는 동적 배열
  • for : 지정한 조건에 따라 코드를 반복 실행하는 반복문
  • Numbers.Add(i) : 배열에 숫자를 추가하는 함수

이후 무작위로 선택한 숫자를 정답 문자열에 추가하고, 이미 선택한 숫자를 배열에서 제거하면 중복을 방지할 수 있다.

int32 Index = FMath::RandRange(0, Numbers.Num() - 1);

Result.Append(FString::FromInt(Numbers[Index]));

Numbers.RemoveAt(Index);

FMath::RandRange()는 지정한 범위에서 무작위 정수를 반환한다.

Numbers.Num()은 배열에 저장된 원소의 개수이며, 배열의 인덱스는 0부터 시작하기 때문에 마지막 인덱스는 Numbers.Num() - 1이 된다.

RemoveAt()은 지정한 인덱스의 원소를 제거한다.

따라서 숫자를 선택한 후 배열에서 제거하면 다음 반복에서 같은 숫자가 다시 선택되지 않는다.

핵심은 무작위 숫자를 생성하는 것뿐 아니라, 선택한 숫자가 중복되지 않도록 처리하는 것이다.


2. 플레이어 입력 유효성 검사

2.1 유효성 검사가 필요한 이유

플레이어가 입력한 값이 항상 게임 규칙에 맞는 것은 아니다.

숫자 야구 게임에서는 입력한 숫자가 다음 조건을 모두 만족해야 정상적인 입력으로 인정된다.

입력값결과이유
386정상서로 다른 3자리 숫자
12오류3자리가 아님
1234오류3자리를 초과
112오류숫자 중복
305오류0 포함
1a3오류숫자가 아닌 문자 포함

이번 과제에서는 잘못된 입력을 하더라도 시도 횟수가 차감되지 않도록 처리해야 했다.

따라서 숫자를 판정하기 전에 입력값이 유효한지 먼저 검사하는 과정이 필요했다.

2.2 문자열 길이 검사

입력값이 정확히 3자리인지 확인하기 위해 FString의 Len() 함수를 활용할 수 있다.

if (InNumberString.Len() != 3)
{
    return false;
}
  • Len() : 문자열의 길이를 반환
  • != : 두 값이 서로 다른지 비교
  • return false : 조건을 만족하지 않는다는 결과를 반환

입력한 문자열의 길이가 3이 아니라면 이후 판정 로직을 진행하지 않는다.

2.3 숫자와 중복 여부 검사

문자열의 길이가 3이라고 해서 모두 정상적인 입력은 아니다.

숫자가 아닌 문자가 포함되어 있거나, 0이 포함되어 있거나, 같은 숫자가 반복되는 경우도 검사해야 한다.

if (!FChar::IsDigit(C) || C == '0')
{
    return false;
}

FChar::IsDigit()은 해당 문자가 숫자인지 확인하는 함수다.

  • ! : 조건의 참과 거짓을 반대로 바꾸는 논리 부정 연산자
  • || : 두 조건 중 하나라도 참이면 참이 되는 OR 연산자
  • == : 두 값이 같은지 비교하는 연산자

즉, 숫자가 아니거나 0이 포함된 경우 정상적인 입력이 아니라고 판단한다.

중복 여부는 TSet과 같은 자료구조를 활용하여 검사할 수 있다.

TSet은 중복되지 않는 값을 저장하기 때문에, 이미 등장한 숫자가 다시 입력되었는지 확인하는 데 활용할 수 있다.

2.4 입력 검사와 시도 횟수 처리 순서

이번 과제에서 중요했던 부분은 입력값의 유효성을 확인하기 전에 시도 횟수를 증가시키지 않는 것이었다.

[플레이어 숫자 입력]
          |
          v
    입력값 유효성 검사
          |
      정상 입력인가?
       /        \
     아니오      예
       |         |
       v         v
   오류 안내   시도 횟수 증가
   횟수 유지       |
                  v
             숫자 판정 진행

ex) 플레이어가 112를 입력했다면 중복 숫자가 포함되어 있으므로 입력 오류로 처리하고, 기존 시도 횟수를 유지해야 한다.

반면 386처럼 정상적인 숫자를 입력했다면 시도 횟수를 1 증가시키고 Strike / Ball 판정을 진행한다.

이처럼 입력 검사를 통과한 경우에만 게임 진행에 영향을 주도록 처리하는 순서가 중요했다.


3. Strike / Ball / Out 판정 로직

3.1 판정 규칙

서버에서 생성한 정답과 플레이어가 입력한 숫자를 비교하여 결과를 계산한다.

판정조건
Strike숫자와 위치가 모두 일치
Ball숫자는 일치하지만 위치가 다름
Out일치하는 숫자가 전혀 없음

ex) 정답이 386일 때 다음과 같이 판정된다.

플레이어 입력판정 결과이유
3863S0B세 자리 모두 일치
3962S0B첫 번째와 세 번째 자리 일치
6380S3B세 숫자가 모두 포함되지만 위치가 다름
124OUT일치하는 숫자가 없음

3.2 Strike 판정

Strike는 숫자와 위치가 모두 일치하는 경우다.

정답과 입력값을 같은 인덱스끼리 비교하여 판정할 수 있다.

if (Secret[i] == Guess[i])
{
    StrikeCount++;
}

Secret[i]는 정답 문자열의 i번째 문자이고, Guess[i]는 플레이어가 입력한 문자열의 i번째 문자다.

두 문자가 같다면 해당 위치의 숫자가 일치한다는 의미이므로 StrikeCount를 증가시킨다.

여기서 ++는 변수의 값을 1 증가시키는 연산자다.

3.3 Ball 판정

Ball은 숫자가 정답에 포함되어 있지만 위치가 다른 경우다.

따라서 같은 위치의 숫자가 일치하지 않았을 때, 해당 숫자가 정답 문자열의 다른 위치에 존재하는지 확인해야 한다.

else
{
    if (Secret.Contains(GuessChar))
    {
        BallCount++;
    }
}

Contains()는 문자열에 특정 문자가 포함되어 있는지 확인하는 함수다.

Strike 판정 이후 else에서 Ball을 검사하면 같은 숫자가 Strike와 Ball로 중복 계산되는 상황을 방지할 수 있다.

Strike는 위치까지 일치해야 하고, Ball은 위치가 달라도 숫자가 포함되어 있으면 인정된다는 차이가 있다.

3.4 Out 판정

정답과 입력값을 모두 비교한 후 Strike와 Ball이 모두 0이라면 Out으로 처리한다.

if (StrikeCount == 0 && BallCount == 0)
{
    return TEXT("OUT");
}

&&는 두 조건이 모두 참일 때만 참이 되는 AND 연산자다.

따라서 Strike가 0이고 Ball도 0인 경우에만 OUT을 반환한다.

그 외에는 Strike와 Ball의 개수를 조합하여 결과를 표시한다.

return FString::Printf(
    TEXT("%dS%dB"),
    StrikeCount,
    BallCount
);

FString::Printf()는 지정한 형식에 따라 문자열을 생성하는 함수다.

%d에는 정수형 값이 들어가므로, 판정 결과를 1S1B, 2S0B와 같은 형태로 반환할 수 있다.


4. 플레이어별 시도 횟수 관리

4.1 최대 3회 입력 제한

이번 숫자 야구 게임에서는 각 플레이어에게 최대 3번의 유효한 입력 기회가 주어진다.

시도 횟수는 PlayerState를 활용하여 플레이어별로 관리했다.

상황시도 횟수
게임 시작0 / 3
정상적인 숫자 입력1 증가
잘못된 숫자 입력변화 없음
3회 입력 완료추가 입력 제한
게임 초기화0 / 3으로 복구

두 플레이어의 입력 횟수는 서로 독립적으로 관리되어야 한다.

ex) Player A가 2번 입력하고 Player B가 1번 입력했다면 각각 2/3, 1/3으로 표시되어야 한다.

4.2 서버에서 관리하는 플레이어 상태

멀티플레이 환경에서는 각 플레이어가 자신의 시도 횟수를 임의로 변경하지 않도록 서버에서 상태를 관리해야 한다.

이번 프로젝트에서는 PlayerState의 시도 횟수 데이터를 Replication을 통해 동기화하고, 각 플레이어의 UI에 반영하도록 구성했다.

[클라이언트 숫자 입력]
          |
          v
      Server RPC
          |
          v
     [서버 입력 검사]
          |
     유효한 입력인가?
          |
          v
    시도 횟수 증가
          |
          v
   [PlayerState 갱신]
          |
      Replication
          |
          v
    [클라이언트 UI]

이를 통해 서버에서 처리한 결과를 클라이언트 화면에서도 확인할 수 있도록 했다.


5. 구현 과정 및 결과

숫자 생성과 판정 로직을 연결한 뒤, Listen Server 환경에서 두 플레이어의 입력을 테스트했다.

특히 잘못된 입력에 대한 처리와 플레이어별 시도 횟수 제한이 정상적으로 동작하는지 확인했다.

  • 정상적인 3자리 숫자 입력 시 판정 결과 출력
  • 숫자 중복, 자리 수 오류, 문자 포함 입력 시 오류 처리
  • 잘못된 입력에 대해서는 시도 횟수 유지
  • 정상적인 입력에 대해서만 시도 횟수 증가
  • 플레이어별 최대 3회 입력 제한
  • 3 Strike 달성 시 승리 처리
  • 두 플레이어 모두 기회를 소진했을 때 무승부 처리

실제 시연에서는 정상 입력과 잘못된 입력을 구분하여 처리하는 과정, 3 Strike 승리, 무승부 및 추가 입력 제한을 확인했다.

이를 통해 입력값 검증부터 숫자 판정, 시도 횟수 관리까지 이어지는 숫자 야구의 핵심 게임 진행 로직을 구성할 수 있었다.


Remind Point

Q1. TArray는 무엇인가?

언리얼 엔진에서 제공하는 동적 배열로, 여러 데이터를 순서대로 저장하고 관리할 수 있다.

Q2. 정답 숫자가 중복되지 않도록 하려면?

이미 선택한 숫자를 배열에서 제거하는 등의 방식으로 같은 숫자가 다시 선택되지 않도록 처리한다.

Q3. 입력 유효성 검사를 먼저 진행하는 이유는?

잘못된 입력이 숫자 판정으로 전달되거나 시도 횟수를 소진하지 않도록 하기 위해서다.

Q4. if와 else를 활용한 Strike / Ball 판정의 차이는?

같은 위치의 숫자가 일치하면 Strike로 처리하고, 그렇지 않은 경우 정답에 숫자가 포함되어 있는지 검사하여 Ball을 판정한다.

Q5. return false는 무엇을 의미하는가?

반환형이 bool인 함수에서 조건을 만족하지 않는다는 결과를 호출한 곳으로 전달한다.

Q6. &&와 ||의 차이는?

&&는 모든 조건이 참이어야 참이고, ||는 조건 중 하나라도 참이면 참이다.

Q7. PlayerState를 활용한 이유는?

각 플레이어의 시도 횟수를 별도로 관리하고, 필요한 상태를 네트워크 환경에서 동기화하기 위해서다.


[학습 후 느낀 점과 개선하고 싶은 점]

: 숫자 야구 게임의 규칙은 비교적 단순하지만, 실제로 구현하려면 정답 생성부터 입력 검사, 숫자 판정, 시도 횟수 관리까지 여러 조건을 순서대로 처리해야 했다. 특히 잘못된 입력이 들어왔을 때 시도 횟수가 증가하지 않도록 하는 부분처럼, 작은 조건 하나가 전체 게임 진행에 영향을 줄 수 있다는 점을 확인했다.

조건문과 반복문을 실제 문제에 적용하는 연습이 더 필요하다 생각했고
추후에는 다음과 같은 부분을 개선하고 싶다.

  • 숫자 생성 및 입력 검사 함수를 직접 다시 작성하며 복습
  • 조건문과 반복문을 활용한 Strike / Ball 판정 로직 연습
  • 함수의 매개변수와 반환값이 어떤 역할을 하는지 정리
  • 입력값에 따른 예외 처리 테스트 보완
  • 요구사항을 작은 조건으로 나누어 구현 순서를 설계하는 연습

이번에는 필수 게임 기능을 완성하는 데 집중했다면, 이후에는 완성된 코드의 동작을 이해하는 것을 넘어 직접 구현할 수 있는 수준으로 복습하고 싶다.

0개의 댓글