https://github.com/pjainxido/numble-find-diff

배포주소: https://numble-find-diff.vercel.app/

Untitled

💻 코드

import type { NextPage } from 'next'
import FindDiffGame from '../components/FindDiffGame'

const Home: NextPage = () => {
  return (
    <>
      <FindDiffGame boardSide={360} timePenalty ={3} timePerStage={15} />
    </>
  )
}

export default Home

next.js로 프로젝트를 생성해 pages 내 index.tsx에 FindDiffgame 컴포넌트를 추가했습니다.

interface IFindDiffGame {
  boardSide: number;
  timePenalty: number;
  timePerStage: number;
}

const FindDiffGame: React.FC<IFindDiffGame> = ({ boardSide, timePenalty, timePerStage }) => {
  const [state, dispatch] = useReducer(gameReducer, initialState);
  const { isPlaying, stage, score, time, answer, defaultColor, answerColor } = state;

전체적인 게임 컴포넌트로, props로 게임판의 한변의 길이, 실패시 패널티 시간, 스테이지당 시간 옵션을 추가하여 커스텀 가능하게 설정했습니다. 해당 컴포넌트에서 관리할 state의 종류가 너무 많아 useReducer 훅을 통해 상태를 관리합니다.

const gameReducer = (state: State, action: Action): State => {
  switch (action.type) {
    case "START_STAGE":
      const blockCount = Math.pow(Math.round((state.stage + 0.5) / 2) + 1, 2);
      const answerIndex = getRandomInteger(0, blockCount - 1);
      const { defaultColor, answerColor } = getRandomPairColor(state.stage);
      return {
        ...state,
        isPlaying: true,
        answer: answerIndex,
        time: action.time,
        defaultColor: defaultColor,
        answerColor: answerColor,
      };
    case "CLEAR_STAGE":
      return {
        ...state,
        stage: state.stage + 1,
        score: state.score + Math.pow(state.stage, 3) * state.time,
      };
    case "TIME_DECREASE":
      return { ...state, time: state.time < action.time ? 0 : state.time - action.time };
    case "RESET_GAME":
      return { ...initialState };
    case "GAME_OVER":
      return { ...state, isPlaying: false };
    default:
      throw new Error("Action type error");
  }
};

리듀서에서는 stage를 시작하며 기본색, 정답색, 정답블록의 인덱스를 설정하는 START_STAGE를 통해 게임을 실행합니다. CLEAR_STAGE는 게임을 클리어하면 실행하며 stage를 +1하고 챌린지 문서에서 요구한 로직대로 score를 더해줍니다.

TIME_DECREAE는 입력한 값만큼 time을 감소시키며, 챌린지에서 예시로 배포한 프로젝트와 동일하게 time값을 0보다 작아질경우 0으로 설정합니다.

RESET_GAME은 initialState로 게임을 초기화 하며, GAME_OVER는 isPlaying 옵션을 false로 수정합니다.

	const blockRowCount = Math.round((stage + 0.5) / 2) + 1;
  const totalBlockCount = Math.pow(blockRowCount, 2);
  const blockSideLength = boardSide / blockRowCount - 4;

  const decreaseTime = (input: number) => {
    dispatch({ type: "TIME_DECREASE", time: input });
  };

  useEffect(() => {
    dispatch({ type: "START_STAGE", time: timePerStage });
  }, [stage]);

  useEffect(() => {
    const timer = setInterval(() => {
      if (isPlaying) decreaseTime(1);
    }, 1000);
    return () => clearInterval(timer);
  }, [isPlaying]);

  useEffect(() => {
    if (time === 0) {
      dispatch({ type: "GAME_OVER" });
      alert("GAME OVER!\\n" + `스테이지: ${stage}, 점수: ${score} `);
      dispatch({ type: "RESET_GAME" });
      dispatch({ type: "START_STAGE", time: timePerStage });
    }
  }, [time]);

  const onSelect = (index: number) => {
    if (index === answer) {
      dispatch({ type: "CLEAR_STAGE" });
    } else decreaseTime(timePenalty);
  };

blockRowCount, totalBlockCount, blockSideLength는 기본적인 stage값과 props로 받는 boardSide로 값을 구할수 있어 state로 사용하는대신 코드내 const값으로 작성했습니다.

useEffect를 통해 대부분의 게임 로직이 동작합니다 .stage가 변할때마다 START_STAGE를 dispath해 자동적으로 게임이 플레이 되게 했습니다. 그리고 isPlaying state를 통해 매 1초마다 시간을 1씩 감소하게했으며, time값이 0이 되는 순간 gameover가 되며, alert로 현재 스테이지와 점수를 출력합니다. 그후 alert창에서 확인을 누르면 게임을 리셋하고 stage를 재시작합니다.

onSelect는 블록을 선택하면 해당 블록의 인덱스와 answer의 값이 일치하면 clear-stage를 dispatch합니다.

값이 다를 경우 timePenalty만큼 시간을 감소시킵니다.