js) 재귀를 사용한 실제 문제 해결

김명성·2023년 7월 26일

요구사항
1.강의 동영상을 보면서 퀴즈가 나타나야 합니다.
2.강의시간은 강의 정보가 담긴 배열에 담겨있으며 강의가 10% 이상 진행 된 뒤 나타나야 합니다.
3.퀴즈는 강의가 끝나기 이전에 등장해야하며, 강의 종료시간 10% 미만에은 등장하지 않아야 합니다.
4.퀴즈의 등장시간은 랜덤한 시간대로 출현하거나 또는 사용자가 정한 고정 시간대에 등장할 수 있어야 합니다.
5.퀴즈의 간격은 총 강의 시간의 10% 이상이어야 합니다. 10분 분량의 강의에 3분이 될 때 퀴즈가 출현했다면, 3분에서 4분 사이에는 퀴즈가 나타나지 않아야 합니다.
6.퀴즈는 총 '알람', '객관식', 'OX'가 있습니다.
7.정답을 제출한 퀴즈는 다시 강의를 볼 때 나타나지 않아야 합니다.

  1. 퀴즈 생성 함수 작성
import { IQuiz, IQuizTime } from "@layouts/Lesson/LessonContentVideo";

export default function getRandomInteger(quiz: IQuiz[],currentLessonPlayTime: number) {

  const quizTimeSet = new Set<number>();
  
  function getRandomInt(currentLessonPlayTime: number) {
    const quizTerm = Math.floor(currentLessonPlayTime * 0.1);
    // currentLessonPlayTime의 10% 미만이어야 함. 추후 상수 quizTerm은 유저의 입력값으로 변경 필요.
    const num = Math.floor( Math.random() * (currentLessonPlayTime - quizTerm));
    let flag = true;

    // currentLessonPlayTime의 10% 미만이어야 함. 추후 상수 quizTerm은 유저의 입력값으로 변경 필요.
    for(let i = 0; i < quizTerm; i ++) {
      if(quizTimeSet.has(num + i) || quizTimeSet.has(num - i)) {
        flag = false;
        break;
      }
    }
    // currentLessonPlayTime의 10% 미만이어야 함. 추후 상수 quizTerm은 유저의 입력값으로 변경 필요.
    if(flag && num > quizTerm){
      quizTimeSet.add(num);
      return num
    } else {
      return getRandomInt(currentLessonPlayTime)
    }
  }

  const newQuiz = quiz.map((q: IQuiz): IQuizTime => {
    if(q.randomTime === true){
      const randomInt = getRandomInt(currentLessonPlayTime);
      return {
        ...q,
        quizOccurTime: randomInt,
        isSolvedQuiz: false
      }
    } 
    else {
      quizTimeSet.add(q.setTimeMin * 60 + q.setTimeSecond);
      return {
        ...q,
        quizOccurTime : q.setTimeMin * 60 + q.setTimeSecond,
        isSolvedQuiz: false
      };
    }
  });
  
  getRandomInt(currentLessonPlayTime)
  return newQuiz;
}
  1. 사용자가 강의 페이지에 접속 했을 때 퀴즈출현시간을 세팅하며, 기존에 로컬스토리지에 담긴 퀴즈를 상태로 가져오거나, 출현 시간에 맞추어 퀴즈를 출력한 뒤 삭제한다.
export default function LessonContentVideo(props: Props) {
 
  const [occurTimeQuiz, setOccurTimeQuiz] = useState<IQuizTime[] | []>([]);
  const [currentQuiz, setCurrentQuiz] = useState<IQuizTime | null>(null);


  // 시간이 되면 퀴즈를 출력한 뒤 로컬스토리지에 담긴 퀴즈를 삭제한다.
  useEffect(() => {
   const flag = setInterval(() => {
      occurTimeQuiz.forEach((quiz:IQuizTime) => {
        if (!quiz.isSolvedQuiz && quiz.quizOccurTime === videoCurrentSeconds.current) {
            videoPlayer.current?.pause();
            setCurrentQuiz(quiz);
            const quizOccurTime = JSON.parse(localStorage.getItem(lessonSeq + ''));
            const filteredQuiz = quizOccurTime.filter((item:IQuizTime) => item.quizOccurTime !== quiz.quizOccurTime);
            localStorage.setItem(lessonSeq as string, JSON.stringify(filteredQuiz));
            setOccurTimeQuiz(filteredQuiz);
        	}
        })
    },1000)
  
    return () => clearInterval(flag)
  },[occurTimeQuiz])

  // 페이지에 접속할 때 로컬에 담긴 퀴즈를 상태로 가져오거나 퀴즈출현시간을 생성한 뒤 상태로 담는다
  useEffect(() => {
    if(!quiz) return;
    const quizTime = JSON.parse(localStorage.getItem(lessonSeq as string));
    if(!quizTime){
      const occureTimeAddedQuiz = getRandomInteger(quiz, props.currentLessonPlayTime)
      localStorage.setItem(lessonSeq as string, JSON.stringify(occureTimeAddedQuiz));
      setOccurTimeQuiz(occureTimeAddedQuiz);
    } else {
      setOccurTimeQuiz(quizTime);
    }
  },[lessonSeq,quiz])


  return (
    <>
      <VideoContainer>
          {
          	quiz?.length > 0 && 
          	<LessonContentVideoModal
            	open={openModal}
	            handleClose={handleCloseModal}
    	        quiz={currentQuiz}
            />
          }
          <VideoPlayer />
      </VideoContainer>
    </>
  1. 3개의 종류로 퀴즈를 나누고, 결과를 나타내는 컴포넌트는 재사용
export function ModalQuiz({open,onCloseModal,quiz,loading = false}: ModalProps) {
  
  const [userSelectedAnswer, setUserSelectedAnswer] = useState('await');
  const [quizItem, setQuizItem] = useState<string[]>([]);
  const [quizAnswer, setQuizAnswer] = useState('');

  const onChoiceAnswer = useCallback((answer: string) => {
    setUserSelectedAnswer(answer);
  },[])

  useEffect(() => {
    if(!quiz) return;
    const { item1 = '',item2 = '',item3 = '',item4 = '',answer } = quiz;
    const temp = [`1. ${item1}`,`2. ${item2}`,`3. ${item3}`,`4. ${item4}`];
    const quizAnswerNumber =  temp.find((item) => item.includes(answer) && item);
    setQuizAnswer(quizAnswerNumber)
    setQuizItem(temp)
  },[quiz])

  return (
    <DialogBox aria-labelledby='modal-title' open={open}>
      {loading || !quiz
      ? <Spinner />
      : (
        <ModalBox>
          <Box>
            <Box>
              <Typography>
                {quiz.alarmContent} {'\n'}
                {quiz.quizContent}
              </Typography>
		{
          (userSelectedAnswer === 'await' && quiz.lessonQuizTypeEnum === "MULTIPLE_CHOICE")
           && <Typography>
               다음 중 맞는 보기를 고르세요.
              </Typography>
          }
        </Box>
      { (quiz.lessonQuizTypeEnum === 'ALARM' && userSelectedAnswer === 'await')
        && <Alarm onClick={onCloseModal} />
      }
      {
        (quiz.lessonQuizTypeEnum === "MULTIPLE_CHOICE" && userSelectedAnswer === 'await')
        && <MultipleChoiceQuiz
            quizItem={quizItem}
            onCloseModal={onCloseModal}
            onChoiceAnswer={onChoiceAnswer} />
      }

      {
        (quiz.lessonQuizTypeEnum === "MULTIPLE_CHOICE" && userSelectedAnswer !== 'await')
        && <QuizAnswerResult
            quizAnswer={quizAnswer}
            userSelectedAnswer={userSelectedAnswer}
            quiz={quiz}
            onCloseModal={onCloseModal}
            onChoiceAnswer={onChoiceAnswer} />
      }
        
      {
        (quiz.lessonQuizTypeEnum === "OX_QUIZ" && userSelectedAnswer === 'await')
        && <OXQuiz
            quizAnswer={quiz.answer}
            itemO={quiz.itemO}
            itemX={quiz.itemX}
            onChoiceAnswer={onChoiceAnswer} />
      }
      {
        (quiz.lessonQuizTypeEnum === "OX_QUIZ" && userSelectedAnswer !== 'await')
        && <QuizAnswerResult
            quizAnswer={quiz.answer}
            userSelectedAnswer={userSelectedAnswer}
            quiz={quiz}
            onCloseModal={onCloseModal}
            onChoiceAnswer={onChoiceAnswer} />
      }
      </Box>
          </Box>
        </ModalBox>
      )}
    </DialogBox>
  );
}
  1. 퀴즈버튼은 재사용. 버튼을 클릭하면 onChoiceAnswer에 의해 await값이 선택한 보기로 바뀌며 <QuizAnswerResult> 컴포넌트가 출력된다.
function MultipleChoiceQuiz({quizItem, onChoiceAnswer}: Props) {
  
  return (
    <Box>
    {
      quizItem.map((item) => (
      <QuizItemButton
        key={item}
        item={item}
        bgColor='rgb(194,51,51)'
        onClick={onChoiceAnswer}
        />
      ))
    }
  </Box>
  )
}
  1. 퀴즈의 정답을 확인 한 뒤 모달 클로즈버튼을 통해 강의를 다시 수강하며 다음 퀴즈를 기다린다
function QuizAnswerResult({onChoiceAnswer, userSelectedAnswer, quiz, quizAnswer, onCloseModal}: Props) {

  const onDeleteSolvedQuiz = useCallback(() => {
    onCloseModal();
    onChoiceAnswer('await');
  },[])

  const isCorrectColor = userSelectedAnswer.split('. ')[1] === quiz.answer ? '#03dd03' : '#ff0000'
  return (
    <>
      <Box
        sx={{
         boxShadow:`2px 2px 2px ${isCorrectColor}`,
         backgroundColor: isCorrectColor,
        }}
        >
        <Typography> {quizAnswer} </Typography>
      </Box>
    <Box>
      <Typography color={isCorrectColor}>
      {
        userSelectedAnswer.split('. ')[1] === quiz.answer
        ? '(O) 정답입니다.'
        : `(X) 틀렸습니다. \n
          정답은 ${quizAnswer} 입니다.
        `
      }
      </Typography>
      <Typography marginTop='2rem'>
        {quiz.feedback}
      </Typography>
    </Box>
    <Button onClick={onDeleteSolvedQuiz}> 확인 </Button>
    </>
  )
}

export default QuizAnswerResult

0개의 댓글