最近流行っているポケモン自己分析っぽいものを作る[Nuxt.js + Vue.js +vuetify + Vuex]

# はじめに
 最近、Twitterでポケモン自己分析というツイートがよく流れてくる。(以下のようなツイート)
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/322871/0db8ed8b-d3eb-780d-f14c-15ca46a7452c.png">

### 公式サイト

https://recruit.pokemon.co.jp/pokemoncenter/special/

とても面白いので自分で似たようなアプリを作りたくなったので作ってみた。
本記事は、ポケモン自己分析っぽいものをNuxt.jsを作った経験をまとめたものである。

:::note warn
NuxtやVueの細かい解説はしていません。
また、Nuxt初心者のため、文法的に間違っている部分があった場合はコメントお願いします！
明らかに冗長なコードを書いている自覚がありましたが、修正方法が分からないのでそのまま進みました。
:::

:::note info
コードの詳細の解説は次回の記事で行う予定です！
:::

<br>
<br>

# 完成品
最初に完成品をお見せするとこんな感じです。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/322871/02d753fa-aa97-9f80-6395-c4492fb4343d.gif">


デプロイサイトは以下になります。

https://poke-selfanalysis-git-main-haruharu3220.vercel.app/




# 仕様検討
色々本家サイトを触ってみて分かったことがある。(ただし、試行回数が少ないので、間違っている可能性がある)
### 本家の仕様(推測有り) 
* **質問は16問**
* **回答は4択(はい、いいえ、どちらかといえばはい、どちらかといえばいいえ)**
* **出題される質問は常に固定**(前の質問の回答に影響を受けない)
* **16問の回答が全く同じ場合は同じ結果になる。**
* **回答が異なっても同じ結果になる場合もある**(試行していないが鳩の巣原理上100%断言できる(鳩の巣原理については参考サイトを記事の後ろに貼っておく))
* **回答以外で結果に影響を与える要素は無い**(一方は質問が表示しきる前に回答し、もう一方は質問が出題されて5秒待機して回答した。どちらも同じ選択肢を選んだ。すると同じ結果になった。(回答時間は影響影響なさそうだが確定ではない))
* **回答は初代ポケモン(No1~No151)のみ??**
 <br>
   <br>
   
一方で分からないことは以下の通り
* **回答結果からポケモンを選定するロジック**
<br><br><br>

ただし、上記は今回の本質ではない為、自分で検討することにした。
上述を参考に今回は簡易版として開発をするので以下の仕様に決めました。


### 個人開発で作るアプリの仕様
* **質問は3問**
* **回答は4択(はい、いいえ、どちらかといえばはい、どちらかといえばいいえ)**
* **4択の回答に0~3の値を振っておき、選択された回答の値の合計値(0点~9点)を算出後、1点~151点に正規化することで結果を決定する**
* **それ以外の仕様は上述の本家仕様と同等とする**
<br>
<br>
<br>
<br>

# 実装 
### 全体像
Nuxt.jsを使って実装を進めます。
技術選定理由は特になく、現在NuxtとVueの勉強をしているというだけです。。(Nuxtの公式ページはこちら)

https://nuxtjs.ir/

ページ構成は以下のようにしました。
* トップページ
* 質問１ページ
* 質問２ページ
* 質問３ページ
* 結果発表ページ

そして、Vuexで何の選択肢を選んで現在何点かを管理しました。Vuexの公式サイトはこちら

https://vuex.vuejs.org/ja/

また、結果発表ページで最終得点を正規化し、得点に応じたポケモンの情報をAPIから取得するメソッドを作成しました。
使用したAPIは「PokeAPI」というものです。

https://pokeapi.co/

また、画面デザインはVuetifyというUIフレームワークを使ってコンポーネントを流用しました。(画面と呼べるほど作って無いですが、、)
公式サイトはこちら。

https://vuetifyjs.com/en/

<br><br>

今回のコードは以下のリポジトリを見れば確認できます。

https://github.com/haruharu3220/poke_selfanalysis

では、次からいよいよ実装に入ります〜

<br>
<br>
<br>
<br>

### トップページの作成
トップページの実装に入ります。
と、いってもボタンを押したら1問目の質問に遷移するボタンを作っているだけです。
<v-sheet>ってタグがVuetifyのおかげで使えるようになったコンポーネントでデザインを一から作る必要がなくなります。

```javascript:index.vue
<template>
  <v-sheet
    elevation="12"
    max-width="600"
    rounded="lg"
    width="100%"
    class="pa-4 text-center mx-auto mt-4"
  >
    <h2 class="text-h5 mb-6">ポケモン自己診断</h2>

    <p class="mb-4 text-medium-emphasis text-body-2">
      ポケモン自己診断っぽいものを作ってみました！
      <br />
      遊んでね！
    </p>
    <!-- 罫線(不要なので外す)
      <v-divider class="mb-4"></v-divider> -->

    <div class="text-center">
      <v-btn
        class="text-none"
        color="yellow"
        rounded
        variant="flat"
        width="200"
        to="/questions"
      >
        　診断をはじめる
      </v-btn>
    </div>
  </v-sheet>
</template>
<style>
.custom-link {
  text-decoration: none;
  color: black;
}
</style>
<script lang="js">

export default {
  data () {
    return {
    }
  },
  methods: {
    startAnalysis () {

    }
  }
}
</script>

```
<br>
<br>
<br>
<br>


## 質問ページの作成
次に質問ページの実装です。
質問と選択肢を表示します。
選択肢をクリックすると次のページに遷移しつつ、点数を加算します。
<br>
本来は質問の数に関わらず、一つのVueファイルで複数の質問と選択肢を管理するべきだと思っていたのですが(変数かなんかを参照して質問と選択肢だけ切り替える)、
やり方が分からず質問の数だけファイルを作ってしまった。。。
リファクタリングができるように勉強をしたいと思います。

ルーディングの設定、質問の設定、選択肢の設定など
細かい箇所を変えながら３つのファイルを作成しているが、本質的に同じことなので一問目のファイルだけ紹介します。

どの選択肢を選択しても2問目の画面に遷移するようルーティングを設定していますが、選択肢ごとに加算されるポイントが違います。(0点~4点)

そしてページが遷移しても点数は覚えておかないといけないので、vuexのstore.stateという機能を使って点数を管理します。(ここの解説も自信が無いので要勉強します)

```javascript:question.vue
<template>
  <v-sheet
    elevation="12"
    max-width="600"
    rounded="lg"
    width="100%"
    class="pa-4 text-center mx-auto mt-4"
  >
    <h2 class="text-h5 mb-6">第1問</h2>

    <p class="mb-4 text-medium-emphasis text-body-2">
      あなたは明るいと言われますか？
    </p>
    <v-divider class="mb-4"></v-divider>

    <div class="text-center">
      <v-btn
        class="text-none ma-2"
        color="yellow"
        rounded
        variant="flat"
        width="200"
        to="/questions2"
        @click="$store.commit('increment', 3)"
      >
        はい
      </v-btn>
      <!-- {{ $store.state.point }} -->
      <v-btn
        class="text-none ma-2"
        color="yellow"
        rounded
        variant="flat"
        width="200"
        to="/questions2"
        @click="$store.commit('increment', 0)"
      >
        いいえ
      </v-btn>
      <v-btn
        class="text-none ma-2"
        color="yellow"
        rounded
        variant="flat"
        width="200"
        to="/questions2"
        @click="$store.commit('increment', 2)"
      >
        どちらかといえばはい
      </v-btn>
      <v-btn
        class="text-none ma-2"
        color="yellow"
        rounded
        variant="flat"
        width="200"
        to="/questions2"
        @click="$store.commit('increment', 1)"
      >
        どちらかといえばいいえ
      </v-btn>
    </div>
  </v-sheet>
</template>

<script>
export default {};
</script>

<style></style>

```
<br>

tore.stateで点数を管理すると書きましたが、実際にはstoreフォルダの中にindex.jsファイルを新規作成し、以下のコードを書きました。
```javascript:index.js
export const state = () => ({
  point: 0,
});

export const mutations = {
  increment(state, value) {
    state.point += value;
  },
};

```




<br>
<br>
<br>
<br>



### 結果発表ページ
最後に結果発表ページです。
0点から9点の点数を1点から151点に正規化することで初代ポケモンのフシギダネからミュウまでのどれかを検索するようにしています。
どうやって検索しているかというと以下になります。this.finalResultPointが正規化後の点数で、
このURLを呼ぶことでポケモンを検索しています。

```javascript
this.url = `https://pokeapi.co/api/v2/pokemon/${this.finalResultPoint}`;
```


コードの全体は以下になります。

```javascript:
<template>
  <v-sheet
    elevation="12"
    max-width="600"
    rounded="lg"
    width="100%"
    class="pa-4 text-center mx-auto mt-4"
  >
    <h2 class="text-h5 mb-6">ポケモン自己診断 結果発表</h2>

    <p class="mb-4 text-medium-emphasis text-body-2">
      あなたをポケモンに例えると...
      <!-- {{ $store.state.point }} -->
    </p>
    <v-divider class="mb-4"></v-divider>
    <p>{{ this.name }}です。</p>
    <div class="text-center"></div>
    <img :src="pokemonImage" />

    <br />

    <v-btn
      class="text-none ma-2"
      color="#1da1f2"
      rounded
      variant="flat"
      width="200"
      @click="twitterShare"
    >
      シェアする
    </v-btn>
  </v-sheet>
</template>
<style>
.custom-link {
  text-decoration: none;
  color: black;
}
</style>
<script lang="js">
const options = {
    method: 'GET',
}

export default {
    data () {
        return {
            name:'',
            pokemonImage:'',
            finalResultPoint: 1,
            url:'',

    };
  },
  methods: {
    normalizePoint() {
        const point = this.$store.state.point; // $store.state.point を変数 point に代入
        const normalizedPoint = Math.floor((point / 9) * 150) + 1;
        console.log(normalizedPoint);
        this.finalResultPoint = normalizedPoint;
        this.url = `https://pokeapi.co/api/v2/pokemon/${this.finalResultPoint}`;
    },
    async getPokemonImage(){
        const response = await fetch(this.url,options)
        .then(response => response.json());
        console.log(response);
        this.pokemonImage = response.sprites.front_default;

        // ポケモン種族情報取得.
        const speciesUrl = response.species.url;
        console.log(speciesUrl);
        const responseSpecies = await fetch(speciesUrl,options)
        .then(response => response.json());
        console.log("★★★★★★");

        console.log(responseSpecies);

        // 名前.
        const names = responseSpecies.names;
        const name = names.find((v) => v.language.name == "ja").name;

        this.name = name;
        // this.pokemonImage = response;
    },
    twitterShare(){
        //シェアする画面を設定
        var shareURL = 'https://twitter.com/intent/tweet?text=' + "ポケモン自己診断っぽいものを作ってみました。" + "診断結果はこちら"+'&url=' + this.pokemonImage ;
        //シェア用の画面へ移行
        location.href = shareURL
        }
  },
  created() {
    this.normalizePoint();
    this.getPokemonImage();
  },
}
</script>

```




<br>
<br>
<br>
<br>


# 作ってみて感想
Nuxtでアプリを作るのが初めてだったのでNuxtの旨味を活かせていないと思いますが、自分の理解を深める良い勉強になりました。やっぱりvueは書いていて楽しいなと思いました。
自作APIを設計できるようになりたいと思いました。また、冗長なコードではなくエレガントなコードが書けるようになりたいと思いました。



# 参考にしたサイト
上述で紹介したサイトの記載は省略しています。

**【Vue.js2&Vue.js3対応】基礎から【Vuetify】を使った応用まで！ 超初心者から最短距離でレベルアップ**

https://www.udemy.com/course/vuejs-basic/

Vueの勉強はこのUdemy動画で行いました。かなりボリュームがあってまだまだ全部を見切れていませんし、後半の動画は僕には理解できない箇所が多いのですが、解説が丁寧です。**しかもめちゃくちゃわかりやすい説明です！ただ、僕の理解力が足りず。。。**

<br><br>

**ポケモンAPIでデータを取る方法**

https://note.com/maclove/n/ne6f7ff096216?magazine_key=mc7ec266b676f

ポケモンAPIの使い方を参考にさせていただきました。
<br>
<br>

**みんな大好きポケモン！PokeAPIで画像と図鑑情報を取得しTwitter APIでつぶやく**

https://tensei-shinai.com/2022/05/02/poke-api/#toc5

ポケモンAPIの使い方を参考にさせていただきました。

<br>
<br>


**【ゆっくり解説】数学的に100%証明可能!?鳩ノ巣原理で論理的に考えよう!**

https://www.youtube.com/watch?v=G0yfUV7ttsA

回答が異なっても同じ結果になる場合もあると断言するために利用した鳩の巣原理についてめっちゃ分かりやすく解説してくれている動画です。
<br>
<br>

# 最後に
コードが少しずつ書けるようになって、面白いアプリの模写を自分で作れるようになってきました。とても嬉しい！
ただ、状態管理や非同期通信、コードの再利用化といったちょっとレベルが高いことになるとまだまだという感じなのでこれからも勉強頑張りますー