1.はじめに
先日、久しぶりにポケモンの対人対戦をやっていたときに、
「相手(ポケモン)の弱点何???」
といった疑念を抱き、対戦時間と戦略を見失いモチベーションが下がってしまいました。
見た目だけだとタイプが分からないポケモンもいるし、タイプが2つあるポケモンもいるとなると覚えられない、、と悩んでました。
これって対戦初心者にありがちでは・・・
と思ったのがきっかけで、これを解決したいと思い
簡単・気軽・素早く弱点を知れるもの作ってみよう!!
と考えて実際に作ってみました。
本記事は、以下のようなコンセプトの記事になります。
・製作の経緯と意図
・簡単な製作の手順とコードのポイントの説明
・初心者向け(今後外部APIを使用したいと考えている方)
といった記事になります。
注意
実際には、とくせいや環境変化によってバトル戦略は変化すると思うので、タイプ相性だけではガチ対戦者さんにとっては不向きかと思います。あくまで、LINE botを作ってみよう!という趣旨です。
また、実際のゲームに基づいていますので、掲載において不適切な表現等が判明した場合は、真摯に受け止め対処致します。
2.作ったもの
LINEで、相手ポケモンの名前を入力して送信する
これだけ!!!
自動で弱点を返答して教えてくれます。
またおまけ程度ですが、ゲーム画面上では2倍弱点と4倍弱点の違いがないので
(どちらも【効果ばつぐん】の表記で2倍なのか4倍なのかは分からない。)
4倍弱点のタイプがあるときはメッセージで分かるようにしました。
シンプルですが、自分も含めた対戦初心者にとってはちょっとは使えると思えるものになりました。
言語については、php(Laravel)を使用しています。
3.なぜ作ったのか
シンプルな答えが欲しいと思ったから。
ポケモン対戦初心者勢の私がまず知りたいと思ったのはタイプ相性でした。
攻略サイトにはタイプ相性が記載されているのですが、とくせい、性格などタイプ相性以外など様々な情報が載っています。
初心者にとって対戦中には情報が多すぎて迷ってしまい判断を鈍らせるので、シンプルに弱点だけ教えてくれるものもあっていいかと思って製作しようと思いました。
LINE Messaging APIを使おうと思った理由
・ポケモンは数が多いので、チェックボックス等の選択肢から選ぶ方式だと時間がかかりすぎる(入力した方が早い。)
・対話式なので答えを知るにはうってつけである。
・単純にLINE Messaging APIを使ってみたかった。
Laravelで製作した理由
今回はDBを用意していないし、単純に入力された値を元に返すだけなのでわざわざLaravelに書く必要はなかったかもしれない。
理由としては、
・DB持った時に対応できそう(ポケモンの追加など)
・UIの管理画面上で色々できそう。(将来的に)
・アクセストークンなど退避して書く事に慣れている。API通信が安全に行える。
・配列を多く扱うと思ったので、使い慣れているPHPを使おうと思った。
・デバッグがしやすい。
辺りが理由でした。
LINE Messaging APIで他の方が作られているのを見ると、JSで書いている方が多い印象を受けましたが、今回はPHPで書くことにしました。
4.製作手順
4-1. LINE Developersに登録する。
まず、LINE Developersに登録します。
LINEに既に使っている場合は、登録した際のアカウントでDevelopersにも登録できます。
特に理由がなければ、使っているアカウントでそのまま登録していいかと思います。
LINEのBot開発 超入門(前編) ゼロから応答ができるまで
https://qiita.com/nkjm/items/38808bbc97d6927837cd
こちらの記事を参考しました。
頻繁にUIが変わっていますが、必要な情報は
・チャネルシークレット
・チャネルアクセストークン(長期)
の二つです。後でWebhookの利用を設定するのでログインしたままにしときます。
4-2. Laravel プロジェクトの立ち上げと初期設定
$laravel new laravel-project
名前は自由で。
.envファイルの設定
envは隠しファイルなので普通は見れないです。
macの場合はcommand + shift + . を押すと表示されます。
envファイルを開いて、4-1で記載されたLINEの情報を記述します。
LINE_CHANNEL_SECRET = "チャネルシークレット"
LINE_ACCESS_TOKEN = "チャネルアクセストークン"
config.servicesに登録
envファイルを直接参照してもいいのですが、本番用と環境用でenvに書いてある設定(DB接続情報など)が違う場合は不便なので、config.servicesからenvの内容を参照する様にします。
   return [
      //省略
      'line' => [
          'access_token' => env('LINE_ACCESS_TOKEN'),
          'channel_secret' => env('LINE_CHANNEL_SECRET'),
       ],
    ]
後でコントローラから上記を参照する様にします。
4-3.LINE Messaging API SDK for PHP をインストール
LINE公式から開発のためのSDK(Software Development Kit)が用意されているのでコンポーザを使ってインストールします。
composer require linecorp/line-bot-sdk
インストールすることで、LINEが用意してくれたクラス・メソッドなどを使う事ができる様になります。
4-4.ルーティングを設定する
LINEからAPIで通信を受けたときに、どのコントローラに処理をお願いするかを記述します。
今回はブラウザを介さずにコントローラを呼び出すのでroutes/api.phpに記述します。
Route::post('/pokename', 'LineBotPokenameController@input_pokemon');
LINEからapi/pokenameに通信を受けたときに、LineBotPokenameControllerクラスのinput_pokemonメソッドを実行しなさい。という意味になります。
4-5で実際にコントローラを作ってきます。
4-5.コントローラを作成する
php artisan make:controller LineBotPokenameController
Larabelプロジェクトのルートフォルダで上記artsianコマンドを打ちます。
ここでは、4-4で設定したルートで同じコントローラ名(LineBotPokenameController)で作成します。
開きます。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class LineBotPokenameController extends Controller
{
    public function input_pokemon(Request $request)
    {
       //ここに書いていく
    }
}
こんな感じになっているかと思います。
続けて、input_pokemonメソッドに愚直に処理を書いてきます。
4-6.クラスを利用してAPIを受け取るための設定をする
LINE Messaging API SDK for PHPをインストールしたことで使えるクラスを追加します。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use LINE\LINEBot;  //追加
use LINE\LINEBot\HTTPClient\CurlHTTPClient;  //追加
use LINE\LINEBot\Event\MessageEvent\TextMessage;  //追加
class LineBotPokenameController extends Controller
//省略
上記ではuseを使い、コントローラを呼び出します。
次にinput_pokemonメソッド内で、インスタンス化します。(下記)
その他にも、LINEから情報を受け取った際に、署名の検証などの情報も一緒に記載します。
//省略
    public function input_pokemon(Request $request)
    {
        //認証を行う
        $lineAccessToken = config('services.line.access_token');
        $lineChannelSecret = config('services.line.channel_secret');
        $httpClient = new CurlHTTPClient($lineAccessToken);
        $lineBot = new LINEBot($httpClient, ['channelSecret' => $lineChannelSecret]);
        $signature = $request->header('x-line-signature');
        if (!$lineBot->validateSignature($request->getContent(), $signature)) {
            //送信元に400エラーを伝える
            abort(400, 'Invalid signature');
        }
        //LINEで入力されたメッセージ情報を受け取る
        $events = $lineBot->parseEventRequest($request->getContent(), $signature);
    }
//省略
最後にある$eventsの所で、LINEからの送られてきたテキスト情報を受け取って入れ込んでます。
が、今の段階では通信がうまくいきません。
LINE DevelopersのWebhookの部分にURLを記載していないからです。
Webhookの設定をしないといけないのですが、こちらはローカルPC内では作動してくれないため、インターネット上でのURLを記載しないといけません。
やり方としては、herokuなどホスティングサービスを利用し、インターネット上でのURL(https://)を取得した後に、LINE DevelopersのWebhook URLの欄にURLを記載します。
(私は、ローカル上でのURLを一時的にインターネットでのURLに変換してくれるngrokを利用しました。)
記載するURLはhttps://herokuなどのURL/api/pokenameとなります。
うまくいくと、LINEからメッセージを送ったときに$eventsに入力したテキストの情報が入ります。
デバッグで変数$eventsを見てみます。
[2020-08-02 20:47:55] local.DEBUG: array (
  0 => 
  LINE\LINEBot\Event\MessageEvent\TextMessage::__set_state(array(
     'emojis' => NULL,
     'message' => 
    array (
      'type' => 'text',
      'id' => '12428220966824',
      'text' => 'ピカチュウ',
    ),
     'event' => 
    array (
      'type' => 'message',
      'replyToken' => 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
      'source' => 
      array (
        'userId' => 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
        'type' => 'user',
      ),
      'timestamp' => 1596368874886,
      'mode' => 'active',
      'message' => 
      array (
        'type' => 'text',
        'id' => '12428220966824',
        'text' => 'ピカチュウ',
      ),
    ),
  )),
)  
こんな感じで情報を受け取ってくれます。
ここまでで、設定完了です!!
後は、返信をするための処理を書いていきます。
4-7.返信するための処理をコントローラーに記述する
今回作成したものは、この様に書きました。
作りたいアプリによって自由に書いてみるといいかもです。
   public function input_pokemon(Request $request)
    {
        //省略
        foreach ($events as $event) {
            if (!($event instanceof TextMessage)) {
                continue;
            }
            $replyToken = $event->getReplyToken();
            $replyText = $event->getText();
            //エンコードを行う
            $replyText = mb_convert_encoding($replyText,'UTF8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN');
            //Jsonを取得する
            $url = public_path() . '/data/double_weakness.json';
            $json = file_get_contents($url);
            $json = mb_convert_encoding($json, 'UTF8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN');
            $type_array = json_decode($json, true);
            //ポケモン情報を取得
            $url = 'https://raw.githubusercontent.com/kotofurumiya/pokemon_data/master/data/pokemon_data.json';
            $json = file_get_contents($url);
            $json = mb_convert_encoding($json, 'UTF8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN');
            $pokemon_array = json_decode($json, true);
            //入力チェック
            if(in_array($replyText, array_column($pokemon_array, 'name'), true))
            {
              //該当するポケモンがいる場合
              $pokemon_data = $pokemon_array[array_search($replyText, array_column($pokemon_array, 'name'))];
              //タイプを入れる
              $type1 = $pokemon_data['types'][0];
              //単タイプかどうか
              if(isset($pokemon_data['types'][1])){
                $type2 = $pokemon_data['types'][1];
              }else{
                $type2 = 'mono_type';
                $sp_weak = "";
              }
              //タイプ判定
              foreach($type_array as $key1 => $value1){
                $array_list = $value1;
                //タイプ1
                $type_match1 = array_filter($array_list, function($element) use($type1)
                {
                  //必ずある前提なので返すだけ
                  return $element['type'] == $type1;
                });
                //該当するものを取得
                $type_a = array_column($type_match1, 'double_weakness_type');
                //タイプ2
                if ($type2 === 'mono_type'){
                  $type_match = $type_a[0];
                }else{
                  $type_match2 = array_filter($array_list, function($element) use($type2)
                  {
                    return $element['type'] == $type2;
                  });
                  //該当するものを取得
                  $type_b = array_column($type_match2, 'double_weakness_type');
                  //複合弱点をマージ
                  $type_match = array_merge($type_a[0],$type_b[0]);
                  //4倍弱点
                  $sp_weak = array_intersect($type_a[0],$type_b[0]);
                  //2倍弱点
                  $type_match = array_diff($type_match,$sp_weak);
                }
                //重複削除
                $type_match = array_unique($type_match);
                $type_match = array_values($type_match);
              };
            }else{
              //ポケモン以外の入力の場合
                $type_match = array("no_much");
            }
            //変数宣言
            $weak_text = "";
            $spweak_text = "";
            //タイプ以外の入力がされてきた場合
            if(in_array("no_much", $type_match)){
              $weak_text = "ポケモンのなまえを入力してね";
            }else{
              for($i = 0; $i < count($type_match); $i++)
              {
                $weak_text = $type_match[$i].",".$weak_text;
              }
              if($sp_weak){
                $weak_text = $weak_text. "\n \n4倍弱点だよ \n".$sp_weak[0];
              }
            }
            //LINEへ送信する
            $lineBot->replyText($replyToken, $weak_text);
        }
    }
いくつかポイント
・ポケモンのデータはgithub上でJSONデータを配布されている方から拝借しました。
  https://github.com/kotofurumiya/pokemon_data
・通常の2倍弱点を一覧にしたJSONデータをLaravelのパブリックに置いて情報を取得しています。基本的には入力されたポケモンの弱点と、JSONで取得したデータを比較してるだけです。
・配列を多用しているので、加工したり制御する関数を多く使っています。
(もっといい書き方があると思う。)
・ポケモン以外の名前が入力されて来た時、タイプが一つだけのポケモン、複合タイプのポケモンの3パターンがあると想定して書いています。
5.実際に使ってみて
使いやすかった!! LINEめっちゃ便利!!
画面に相手の名前が書いてあるので入力に迷うことはないし、限られた時間の中で素早く弱点が分かるのは結構便利だった。
まあ、勝てるかどうかは別として。
・・・対戦初心者には十分活用できるかなと思いました!!!
6.反省点
・例外処理(仮にJSONを取得できなかったときに処理が止まる)を直さないといけない。
・一応自分としてはやりたい事ができたが、書き方の部分では甘い部分があるはずなので満足しない。
・これだけだったらあまりLaravelで書く意味がないので、DBを持ったり一覧表示や修正をUI上で行える様にすれば多少は意味が出てくるかもしれないです。
7.最後に
Line Messaging APIはリファレンスも丁寧な日本語で分かりやすくて、始める壁も低く感じました。
あとは、自分で使って便利なだと思って制作できたので楽しく作れました。
Qiitaとかも結構参考になる記事が多くて助かったので、外部APIを使ったアプリ作成のとっかかりとしてもLINEはいいと思います!!おすすめ!!
アイデア次第で他にも結構やれることもありそうなので、思いついたらまた何か作ってみようと思います!

