Utility Village 숫자 야구는 서로 다른 4개의 숫자로 이루어진 비밀 코드를 7번 안에 맞추는 게임입니다. 이번 포스트에서는 게임 로직과 힌트 알고리즘을 설명합니다.

규칙

  • 비밀 코드: 0~9 중 서로 다른 4개의 숫자 (첫 자리 0 허용)
  • 추측: 마찬가지로 서로 다른 4자리 숫자 입력
  • 결과: 스트라이크(숫자와 위치 모두 일치) / (숫자는 있지만 위치 다름)
  • 제한: 최대 7번의 시도, 최대 2번의 힌트

비밀 코드 생성

function createSecretCode(): string {
  const digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
  const result: string[] = [];
  
  for (let i = 0; i < 4; i++) {
    const idx = Math.floor(Math.random() * digits.length);
    result.push(digits[idx]);
    digits.splice(idx, 1); // 선택된 숫자 제거로 중복 방지
  }
  
  return result.join('');
}

첫 자리에 0이 오는 경우도 허용합니다. "0123"은 유효한 비밀 코드입니다.

스트라이크/볼 계산

interface GuessResult {
  strikes: number;
  balls: number;
}

function evaluateGuess(secret: string, guess: string): GuessResult {
  let strikes = 0;
  let balls = 0;
  
  for (let i = 0; i < 4; i++) {
    if (guess[i] === secret[i]) {
      strikes++;
    } else if (secret.includes(guess[i])) {
      balls++;
    }
  }
  
  return { strikes, balls };
}

힌트 알고리즘: 정답 후보 역추적

게임 중 최대 2번 사용할 수 있는 힌트는 현재까지의 추측 기록과 일치하지 않는 숫자를 제외합니다.

서로 다른 4자리 숫자 조합의 전체 경우의 수는 다음과 같습니다.

10 × 9 × 8 × 7 = 5,040개

그러나 실제 비밀 코드는 어떤 숫자든 첫 자리가 될 수 있으므로 5,040개가 전부입니다. (단, 첫 자리가 0인 경우도 포함합니다.)

// 사전에 모든 가능한 정답 목록 생성 (5,040개 → 실제로는 중복 제거 후)
function createAllSecretCodes(): string[] {
  const codes: string[] = [];
  for (let a = 0; a <= 9; a++)
  for (let b = 0; b <= 9; b++) { if (b === a) continue;
  for (let c = 0; c <= 9; c++) { if (c === a || c === b) continue;
  for (let d = 0; d <= 9; d++) { if (d === a || d === b || d === c) continue;
    codes.push(`${a}${b}${c}${d}`);
  }}}
  return codes; // 5,040개
}

힌트 계산 과정

  1. 전체 5,040개 후보 중 추측 기록과 일치하는 결과를 내는 후보만 필터링
  2. 남은 후보들에서 한 번도 등장하지 않는 숫자 식별 = 제외 가능한 숫자
function findExcludableDigits(history: GuessEntry[]): number[] {
  const allCodes = createAllSecretCodes();
  
  // 추측 기록과 호환되는 정답 후보만 필터링
  const candidates = allCodes.filter(candidate =>
    history.every(entry => {
      const result = evaluateGuess(candidate, entry.guess);
      return result.strikes === entry.result.strikes &&
             result.balls === entry.result.balls;
    })
  );
  
  // 후보들에 등장하지 않는 숫자 = 정답에 없는 숫자
  const presentDigits = new Set(candidates.flatMap(c => c.split('')));
  
  return [0,1,2,3,4,5,6,7,8,9].filter(d => !presentDigits.has(String(d)));
}

예를 들어 "1234"를 추측해서 0S 0B가 나왔다면, 1, 2, 3, 4는 정답에 없는 것이 확정됩니다. 이런 경우 4개의 숫자를 한 번에 제거할 수 있습니다.

UI 구성

  • DigitSlots: 4자리 입력 슬롯 — 숫자 키보드 또는 방향키로 조작
  • GuessHistoryTable: 추측 기록과 S/B 결과 테이블
  • HintDigitBoard: 0~9 숫자 보드에서 제외 가능한 숫자를 시각적으로 표시

7번 안에 4S 0B를 달성하면 성공입니다. 시도 횟수가 늘어날수록 힌트의 유효성도 높아집니다.


사이트 바로가기: https://utility.dreamurl.biz/minigame/number-baseball