유저가 입력한 1 이상 99 이하의 정수 를 컴퓨터가 맞춰내는 게임을 만들고 있다.

 

  1. 유저가 입력한 정수를 제외하고 (즉, 프로그램은 첫번째 시도에서 정답을 맞출 수는 없다)
  2. 랜덤 정수를 생성한다.
  3. 이에 따라 유저가 Up or Down 을 입력하면
  4. 프로그램은 그 랜덤 정수를 최대값 or 최소값 으로 업데이트하여
  5. 새로운 랜덤 정수를 생성한다.
  6. 이를 정답을 맞출 때까지 반복한다.

 

(임포트 문은 생략했다)

// min, max : 최소값, 최대값
// exclude: 제외할 정수 (최초 입력값 & 이미 생성한 랜덤값)
const generateNumBetween = ( min: number, max: number, exclude: number): number => {
  const randomNum = Math.floor(Math.random() * (max - min)) + min; // 랜덤 정수 생성

  //컴퓨터는 첫 시도에서 답을 맞출 수 없음.
  if (randomNum === exclude) {
    //생성된 숫자가 제외돼야하는 정수와 같다면 다시 생성
    return generateNumBetween(min, max, exclude);
  } else {
    return randomNum;
  }
};

 

 

매개변수 min, max, exclude는 숫자형이고, 이들을 통해 실행된 함수 generateNumBetween은 숫자형을 반환함을 명시한다.

 

 

랜덤 정수를 생성한 방법을 보다 자세히 설명해보자면 아래와 같다.

 const randomNum = Math.floor(Math.random() * (max - min)) + min
 
 
 
 max - min // 범위 크기 설정 (예: 1부터 100이라면 범위는 99)
 
 Math.random() // 0부터 1사이의 난수 생성
 
 Math.random() * (max - min) // 범위 내의 난수 생성
 
 Math.floor() // 소수점 아래 자리 반올림
 
 Math.floor(Math.random() * (max - min)) // 생성한 랜덤 난수의 소수점 아래 자리 반올림
 
 + min // min 기준으로 원하는 범위로 이동 (현재는 100 - 1 = 99 -> 0부터 99 이므로, 1부터 100으로 교정)
 
 Math.floor(Math.random() * (max - min)) + min // 완성

 

 

이제 게임 화면을 구성한다.

// 타입 선언
interface GameScreenProps {
  userNumber: number;
  onGameOver: () => void;
}

  // 최소값, 최대값 변수 설정
  let minBoundary = 1;
  let maxBoundary = 100;

export default function GameScreen({ userNumber, onGameOver }: GameScreenProps) {
  // 첫번째로 추측할 랜덤 정수
  const initialGuess = generateNumBetween(minBoundary, maxBoundary, userNumber);

  const [currentGuess, setCurrentGuess] = useState(initialGuess);

  useEffect(() => {
    if (currentGuess === userNumber) {
      onGameOver()	// 정답을 맞춘다면 게임오버 화면으로
    }
  }, [currentGuess, userNumber, onGameOver]);	

  //유저가 +,-를 올바른 방향으로 사용하도록 유도
  const nextGuessHandler = (direction: string) => {
    if (
      (direction === "lower" && currentGuess < userNumber) ||
      (direction === "greater" && currentGuess > userNumber)
    ) {
      Alert.alert("정말인가요?", "다시 한 번 생각해보세요.", [
        { text: "아차차", style: "cancel" },
      ]);
      return;	// return을 쓰지 않으면 alert창이 뜨는 동시에 새로운 정수가 생성된다.
    }

    //수를 올릴 방향. 키울지 줄일지?
    if (direction === "lower") {
      maxBoundary = currentGuess;
    } else {
      minBoundary = currentGuess + 1;
    }
    console.log(minBoundary, maxBoundary); //범위값이 계속 업데이트 되는 것을 확인할 수 있다
    
    // 새로운 랜덤 정수 생성
    const newRandomNum = generateNumBetween( minBoundary, maxBoundary, currentGuess );
    setCurrentGuess(newRandomNum); //업데이트된 최소값, 최대값, 최근에 썼던 정수를 배제
  };

 

 

useEffect문의 의존성 배열에 currentGuess, userNumber, onGameOver 가 들어가는 이유?

currentGuess

  • currentGuess와 userNumber가 일치하는지 여부를 확인하여 게임을 종료해야 한다.
  • 따라서 currentGuess가 바뀔 때마다 if (currentGuess === userNumber) 를 검사한다.

userNumber

  • userNumber가 바뀐다는 것은 새로운 게임이 시작되었다는 것을 의미하므로, (유저가 게임 도중 바꾸는 경우 포함( 이 로직을 다시 실행해야한다

onGameOver

  • onGameOver 는 정답 맞췄을 때 실행되는 콜백함수이다.
  • 따라서 onGameOver의 함수 구현이 변경되었다면 useEffect문을 수행해야 한다.

 

 

 

 

마지막으로 리턴문을 작성한다.

  return (
    <View>
      <NumberContainer>{currentGuess}</NumberContainer>
      <View>
        <Text>Higher or Lower?</Text>
        <View>
          <Button onPress={nextGuessHandler.bind(this, "lower")}>
            -
          </Button>
          <Button onPress={nextGuessHandler.bind(this, "greater")}>
            +
          </Button>
        </View>
      </View>
    </View>
  );
}

 

 

 

 


 

 

 

 

그런데 여기서 프로그램이 정답을 맞춘 순간 Render Error: Maximun call stack size exceeded 가 반환된다.

 

 

 

 

이 문제는 렌더링 순서와 관련이 있다.

 

기본적으로 React의 렌더링 순서는 아래와 같다.

  1. 컴포넌트의 모든 함수를 실행
  2. useState 초기화
  3. useEffect 실행

 

이 경우에 대입하여 설명하자면, 렌더링은 아래와 같은 순서로 실행된다.

  1. generateNumBetween 함수를 실행하여 initialGuess 생성
  2. initialGuess 값을 받아 상태를 초기화
  3. initialGuess 와 currentGuess 에 초기값을 설정한 후 useEffect 함수 실행

 

결론

  1. useEffect문은 currentGuess가 변경될 때마다 실행되어 게임 종료 조건을 확인하는 것이 최종 목적인데-
  2. 정답을 고른 순간, nextGuessHandler가 실행되고 setCurrentGuess를 호출하여 상태를 업데이트하고 리렌더링을 준비한다.
  3. 그러나 정답은 minBoundary와 maxBoundary가 같은 경우이므로, 유효한 범위가 없기 때문에 nextGuessHandler는 새로운 랜덤 정수를 생성할 수 없다.
  4. 이 때 generateNumBetween는 유효한 범위를 찾을 때까지 재귀적으로 계속 호출되게 된다.
  5. 따라서 useEffect문보다 먼저 실행되는 generateNumBetween함수가 무한 루프에 걸려 useEffect문은 실행되지 못하고, 이에 따라 재렌더링이 미처 이루어지지 못한다.

 

 

 

해결 방법

절대적으로 안전한 방법일 것 같지는 않아, 추후 더 고민해보기

//이전 코드
const initialGuess = generateNumBetween(minBoundary, maxBoundary, userNumber);

//바꾼 코드
const initialGuess = generateNumBetween(1, 100, userNumber);

 

위처럼 초기 최소값, 최대값을 고정하여 하드코딩 하는 방법이 있다.

이렇게 하면 generateNumBetween은 항상 유효한 범위를 가질 수 있다.

따라서 유효한 범위는 지키는 선에서 에러를 반환하던 함수를 해결하고, useEffect문을 수행해 화면 전환을 이룰 수 있다.

 

+ Recent posts