3
\$\begingroup\$

Here is the JavaScript code:

(function($) {
  const shuffleArray = (arr) => {
    let currentIndex = arr.length;
    let temporaryValue;
    let randomIndex;
    // While there remain elements to shuffle...
    while (currentIndex !== 0) {
      // Pick a remaining element...
      randomIndex = Math.floor(Math.random() * currentIndex);
      currentIndex -= 1;
      // And swap it with the current element.
      temporaryValue = arr[currentIndex];
      arr[currentIndex] = arr[randomIndex];
      arr[randomIndex] = temporaryValue;
    }
    return arr;
  };

  //Each object contains the question, an array of the possible answers, and the correct answer.
  let questions = [
    {
      text: "Which one of Daenerys' handmaidens died in The Red Waste prior to arriving in Qarth ?",
      options: ['Irri', 'Doreah', 'Jhiqui', 'Missandei'],
      answerIndexInOptions: 1
    },
    {
      text: "Which one of the nine free cities of Essos was not originally a colony of Old Valyria ?",
      options: ['Pentos', 'Braavos', 'Volantis', 'Myr'],
      answerIndexInOptions: 1
    },
    {
      text: "Who forged Robert Baratheon's warhammer?",
      options: ['Ironbelly', 'Mikken', 'Donal Noye', 'Tobho Mott'],
      answerIndexInOptions: 2
    },
    {
      text: "During one of Arya's dancing lessons, Syrio Forel talks of an exotic land with lizards with scythes for claws. What continent is he alluding to?",
      options: ['Sothoryos', 'Ulthos', 'Westeros', 'Essos'],
      answerIndexInOptions: 0
    },
    {
      text: "Who does Eddard Stark deem the greatest warrior he ever encountered?",
      options: ['Barristan Selmy', 'Gerold Hightower', 'Oswell Whent', 'Arthur Dayne'],
      answerIndexInOptions: 3
    },
    {
      text: "What house is home to Greywater Watch ?",
      options: ['House Karstark', 'House Reed', 'House Glover', 'House Tallhart'],
      answerIndexInOptions: 1
    },
    {
      text: "Who died at the hands of Catelyn Stark during the Red Wedding?",
      options: ['Aegon Frey', 'Roslin Frey', 'Joyeuse Erenford', 'Ryman Frey'],
      answerIndexInOptions: 0
    },
    {
      text: "Who attempted to kill Myrcella Baratheon in the midst of Arianne Martell's failed plan to put her on the iron throne?",
      options: ['Doran Martell', 'Areo Hotah', 'Gerold Dayne', 'Quentyn Martell'],
      answerIndexInOptions: 2
    },
    {
      text: "Who won the melee at the hand's tourney in King's Landing?",
      options: ['Beric Dondarrion', 'Loras Tyrell', 'Thoros of Myr', 'Balon Swann'],
      answerIndexInOptions: 2
    },
    {
      text: "What are the Lannister's house words?",
      options: ['Growing strong', 'Hear me roar', 'A Lannister always pays his debts', 'We do not sow'],
      answerIndexInOptions: 1
    },
  ];
  questions.shuffleOptions = () => {
    questions.forEach( el => {
      const correctAnswer = el.options[el.answerIndexInOptions];
      el.options = shuffleArray(el.options);
      el.answerIndexInOptions = el.options.indexOf(correctAnswer);
    } );
  };

  const quiz = (function() {
    const getFinalResultsString = (score, questionsCount) => {
      return `Your final score is ${score} out of ${questionsCount}.`;
    };

    const getIncorrectAnswerString = (correctAnswer) => {
      return `You should have chosen ${correctAnswer}.`;
    };

    const showPopUp = (popUp, text) => {
      $('main, header').addClass('body-transparent');
      popUp
        .fadeIn()
        .find('.pop-up-text')
        .text( text );
    };

    const hidePopUp = (popUp) => {
      $('main, header').removeClass('body-transparent');
      popUp.fadeOut('fast');
      popUp.find('pop-up-text').empty();
    };

    const setQuestionCount = (num, questionsCount) => {
      $('.current-question-div').text(
        `Questions: ${num}/${questionsCount}`
      );
    };

    const setScoreCount = (score, questionsCount) => {
      $('.current-score-div').text(
        `Score: ${score}/${questionsCount}`
      );
    };

    const setOption = (answerElem, optionIndex, optionText) => {
      $(answerElem)
        .find('input[type="radio"]')
        .prop('checked', optionIndex === 0)
        .attr( {
          'id': optionText,
          'value': optionText,
          'data-index': optionIndex,
        } );
      $(answerElem)
        .find('label')
        .attr( 'for', optionText )
        .text( optionText );
    };

    const setQuestion = (question) => {
      $('.quiz-questions').text( question.text );
      $('.answer-to-question').each(
        (index, elem) => setOption(elem, index, question.options[index])
       );
    };

    const showResults = (score, questionsCount) => {
      $('.final-results').removeClass('hidden');
      $('.final-score-paragraph').text(
        getFinalResultsString(score, questionsCount)
       );
    };

    const questionsCount = questions.length;
    let currentQuestionIndex = 0;
    let score = 0;

    return {
      start: () => {
        $('.quiz-div').removeClass('hidden');
        $('.start-page-div').addClass('hidden');
        questions = shuffleArray(questions);
        questions.shuffleOptions();
        setScoreCount( score, questionsCount );
        setQuestion( questions[currentQuestionIndex] );
        setQuestionCount( currentQuestionIndex, questionsCount );
      },
      reset: () => {
        currentQuestionIndex = 0;
        score = 0;
        $('.start-page-div').removeClass('hidden');
        $('.final-results').addClass('hidden');
      },
      handleAnswer: () => {
        const isAnswerCorrect = questions[currentQuestionIndex].answerIndexInOptions === Number(
            $('.answer-to-question input:checked').attr('data-index')
          );
        const correctAnswer =
          questions[currentQuestionIndex].options[questions[currentQuestionIndex].answerIndexInOptions];
        if ( isAnswerCorrect ) {
          showPopUp( $('.pop-outer-correct'), '' );
          score++
        }
        else {
          showPopUp(
            $('.pop-outer-incorrect'),
            getIncorrectAnswerString( correctAnswer )
          );
        }
        setScoreCount( score, questionsCount );
      },
      proceed: () => {
        currentQuestionIndex++;
        // if quiz has not ended yet
        if ( currentQuestionIndex < questionsCount ) {
          setQuestion( questions[currentQuestionIndex] );
          setQuestionCount( currentQuestionIndex, questionsCount );
        } else {
          $('.quiz-div').addClass('hidden');
          showResults( score, questionsCount );
        }
      },
      hidePopUp: hidePopUp,
    };
  })();

  const submitQuestion = () => {
    quiz.handleAnswer();
    quiz.proceed();
  };

  const closePopUp = ev => 
    quiz.hidePopUp( $(ev.target).parents('.pop-outer') );


  const events = [
    {
      'type': 'click',
      'selector': '.start-button', 
      'handler': quiz.start,
    },
    {
      'type': 'click',
      'selector': '.start-over', 
      'handler': quiz.reset,
    },
    {
      'type': 'click',
      'selector': '.submit-question', 
      'handler': submitQuestion,
    },
    {
      'type': 'click',
      'selector': '.pop-outer .close', 
      'handler': closePopUp,
    },
  ];

  events.forEach( (ev) => {
    $(document).on( ev.type, ev.selector, ev.handler);
  } );

})($);

Here is the Github repo for the rest of the code: https://github.com/doubleOrt/a-song-of-ice-and-fire-quiz

Here is a runnable fiddle: https://jsfiddle.net/m4um5ek2/

Interested mostly in code structure improvements, but would love feed back on the events object or the event attachment architecture as well (with the loop). Naming improvements are welcome, as is anything else.

P.S please don't hesitate to comment/post even if only for a tiny improvement.

\$\endgroup\$
  • \$\begingroup\$ Is there any particular reason you wrapped everything in an IIFE? Is this being ran as a userscript with something like Tampermonkey? \$\endgroup\$ – Phrancis Apr 2 '18 at 22:58
  • \$\begingroup\$ @Phrancis negative, only the fact that globals are considered evil. \$\endgroup\$ – doubleOrt Apr 2 '18 at 23:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.