Nuxt.js で国別クイズを作成してみた！

# Vue.jsで国別クイズを作成してみた。

## はじめに
最近[DevChallenges](https://devchallenges.io/) とういうサービスを知りました。
DevChallengesは、いろんなWeb開発課題が用意されており、スキルアップを図るサイトです。
おもしろそうなものがたくさんあり、迷ったのですが、今回は[CountryQuiz](https://devchallenges.io/challenges/Bu3G2irnaXmfwQ8sZkw8)というものを試してみました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916672/85ad91cb-6738-a42d-09bf-fb5e84bda5f8.png)

#### 完成品
[Country-Quiz](https://country-quiz-dc.herokuapp.com/)


## 要件

- 1番初めに挑戦するクイズの種類を選べます（国名から6州を当てるクイズ・国旗から国を当てるクイズ）

- クイズは毎回答後に正誤が表示される

- 正解数はカウントされ、最後に結果発表画面を見られる

- クイズは再挑戦できる

## 使用したもの
APIは [REST COUNTRIES](https://restcountries.eu/) を使用しました
フレームワークはNuxt.jsを使用しました。
明らかにオーバースペックなのですが、これから認証機能や、クイズ結果を記録していき、マイページで表示などを実装したいなと思ったのでNuxtにしました！


## 実装したソースコード

```vue
<template>
  <div id="index">
    <div class="auth-box">
      <div v-if="loginUser">
        <a class="logout" @click="logout">ログアウト</a>
      </div>
      <a v-else class="login" @click.prevent="login">ログイン</a>
    </div>
    <div class="index-inner">
      <h1 class="title is-1">COUNTRY QUIZ</h1>
      <img
        v-if="status <= endNum"
        class="decoration"
        src="/images/def.svg"
        alt=""
      />
      <div class="quiz-box">
        <div class="quiz-box-inner">
          <transition mode="out-in">
            <!-- 初期表示 -->
            <div v-if="status === 0" key="init" class="init">
              <h2 class="question title is-2">
                {{ displayQuiz.question }}
              </h2>
              <div class="answers">
                <button
                  v-for="option in displayQuiz.options"
                  :key="option.name"
                  class="button is-medium is-fullwidth"
                  @click="startQuiz(option.name)"
                >
                  {{ option.jaName }}
                </button>
              </div>
            </div>
            <!-- クイズ終了表示 -->
            <div v-else-if="status > endNum" key="finish" class="finished">
              <img src="/images/finish.svg" alt="" />
              <h2 class="question title">FINISH</h2>
              <p class="result">
                あなたは5問中
                <span class="correct-answers">{{ correctAnswers }}</span>
                問正解しました
              </p>
              <button
                class="button is-medium is-fullwidth try-again"
                @click="backMenu"
              >
                メニューに戻る
              </button>
            </div>
            <!-- 質問表示 -->
            <div v-else key="quiz" class="in-progress">
              <div class="progress-wrapper">
                <div ref="meter" class="progress-bar"></div>
              </div>
              <!-- 国旗クイズの場合のみ表示される -->
              <img
                v-if="displayQuiz.flagImageUrl"
                :src="displayQuiz.flagImageUrl"
                class="flag-img"
                alt=""
              />
              <h2 class="question title is-2">
                {{ displayQuiz.question }}
              </h2>
              <div ref="answers" class="answers">
                <button
                  v-for="(option, index) in displayQuiz.options"
                  :key="index"
                  class="button is-medium is-fullwidth"
                  :class="{
                    correct: isExamining && displayQuiz.answer == option.name,
                  }"
                  @click="answer(option.name, $event)"
                >
                  {{ option.jaName }}
                </button>
                <button
                  v-if="isExamining"
                  class="next-btn button"
                  @click="continueQuiz"
                >
                  {{ nextBtnText }}
                </button>
                <!-- 回答後はNEXT以外触れなくします -->
                <div v-if="isExamining" class="touch-prevention"></div>
              </div>
            </div>
          </transition>
        </div>
      </div>
    </div>
    <pre>{{ loginUser }}</pre>
  </div>
</template>

<script>
import axios from 'axios'
import { mapGetters, mapActions } from 'vuex'

const DATA_MAX = 200 // APIのデータ上限
const regionOptions = [
  {
    name: 'Asia',
    jaName: 'アジア州',
  },
  {
    name: 'Africa',
    jaName: 'アフリカ州',
  },
  {
    name: 'Americas',
    jaName: 'アメリカ州',
  },
  {
    name: 'Oceania',
    jaName: 'オセアニア州',
  },
  {
    name: 'Europe',
    jaName: 'ヨーロッパ州',
  },
]

function genRandomArray(array, max) {
  while (true) {
    const randomNum = Math.floor(Math.random() * max)
    if (!array.includes(randomNum)) {
      array.push(randomNum)
      break
    }
  }
}

export default {
  data() {
    return {
      quizzes: [],
      endNum: 5,
      status: 0,
      correctAnswers: 0,
      isExamining: false,
    }
  },
  computed: {
    ...mapGetters(['loginUser']),
    displayQuiz() {
      return this.quizzes[this.status]
    },
    nextBtnText() {
      if (this.status === this.endNum) return '結果を見る'
      else return '次へ'
    },
  },
  created() {
    // 初期表示の設問と選択肢
    this.initQuizzes()
  },
  methods: {
    ...mapActions(['login', 'logout']),
    // クイズを進行していくためのメソッド
    continueQuiz() {
      // 選択した回答のハイライト用classをはずす
      const all = this.$refs.answers.querySelectorAll('button')
      console.log(all)
      all.forEach((el) => {
        el.classList.remove('user-answered')
      })

      // クイズを進める
      this.status++
      this.isExamining = false
    },
    // 回答後に正誤表示を行うメソッド
    examiningAnswer() {
      this.isExamining = true
    },

    // クイズをリセットしてメニューに戻るメソッド
    backMenu() {
      this.status = 0
      this.correctAnswers = 0
      this.initQuizzes()
    },
    // 通信を飛ばして、クイズ作成へ続けるメソッド
    startQuiz(type) {
      axios
        .get('https://restcountries.eu/rest/v2/all')
        .then((res) => {
          res.data = res.data.filter((el) => {
            return el.translations.ja !== null
          })
          if (type === 'region') this.makeRegionQuiz(res.data)
          else if (type === 'flag') this.makeFlagQuiz(res.data)
          this.status = 1
          this.correctAnswers = 0
        })
        .catch((error) => {
          throw new Error(error)
        })
    },

    // 6州クイズを生成するメソッド
    makeRegionQuiz(responseData) {
      const randomNumArray = []
      for (let n = 0; n < 5; n++) {
        genRandomArray(randomNumArray, DATA_MAX)
      }
      console.log(randomNumArray)
      for (let i = 0; i < this.endNum; i++) {
        const country = responseData[randomNumArray[i]]
        const quiz = {
          question: `${country.translations.ja}が属する州はどこですか？`,
          options: regionOptions,
          answer: country.region,
        }
        this.quizzes.push(quiz)
      }
    },
    // 国旗クイズを生成するメソッド
    makeFlagQuiz(responseData) {
      const randomNumArray = []
      for (let n = 0; n < 5; n++) {
        genRandomArray(randomNumArray, DATA_MAX)
      }
      for (let i = 0; i < this.endNum; i++) {
        // 正解を作成
        const country = responseData[randomNumArray[i]]
        const flagOptions = []
        const flagOption = {
          name: country.name,
          jaName: country.translations.ja,
        }
        // 正解セット
        flagOptions.push(flagOption)

        // 不正解問題作成
        const randomNumArray2 = []
        for (let j = 0; j < 4; j++) {
          while (true) {
            const randomNum = Math.floor(Math.random() * DATA_MAX)
            if (
              !randomNumArray2.includes(randomNum) &&
              randomNum !== randomNumArray[i] // 正解をかぶってしまうといけないので&&以下の記述が必要
            ) {
              randomNumArray2.push(randomNum)
              break
            }
          }
          // 不正解問題をセット
          flagOptions.push({
            name: responseData[randomNumArray2[j]].name,
            jaName: responseData[randomNumArray2[j]].translations.ja,
          })
        }
        // 正解が先頭でセットされてしまっているのでsortをかけて分からなくする
        flagOptions.sort((a, b) => {
          if (a.name > b.name) return -1
          else if (a.name < b.name) return 1
          else return 0
        })
        const quiz = {
          flagImageUrl: country.flag,
          question: `この国旗はどこの国のものですか？`,
          options: flagOptions,
          answer: country.name,
        }
        this.quizzes.push(quiz)
      }
    },
    // ユーザーがクイズに回答したときのメソッド
    answer(userAnswer, e) {
      // 選択した答えをハイライトさせる処理
      e.target.classList.add('user-answered')
      // 正解したらカウントして正誤表示を行う
      if (userAnswer === this.displayQuiz.answer) this.correctAnswers++
      this.examiningAnswer()
      this.advanceMeter()
    },
    // クイズの進捗メーターを進めるメソッド
    advanceMeter() {
      const meter = this.$refs.meter
      let percent = 0
      percent += (this.status / this.endNum) * 100
      meter.style.width = percent + '%'
    },
    // quizzesの初期化
    initQuizzes() {
      this.quizzes = []
      const initialQuestions = {
        question: 'どの知識についてクイズをしたいですか？',
        options: [
          {
            name: 'region',
            jaName: '地域区分クイズ',
          },
          {
            name: 'flag',
            jaName: '国旗クイズ',
          },
        ],
      }
      this.quizzes.push(initialQuestions)
    },
  },
}
</script>
```

## おわりに
書いてみた感想は、まだ少し共通化できそうな部分があるなぁという感じです。(クイズ生成あたり)
これから、Firebaseの認証機能とCloudFireStoreを使い、ログインしているユーザーのクイズ結果は記録していけるようにしていこうと思います。
DevChallengesは他にもまだまだ開発課題があるので、当分退屈しなさそうで嬉しいですねー！