Alexa Skillを使ってポケモン対戦を有利に進める

この記事は 面白法人グループ Advent Calendar 2022 の11日目の記事です

はじめに

チャオ

カヤックボンド初代ポケモンチャンピオンの栗野です。
皆さん、ポケットモンスター スカーレット・バイオレット楽しんでますか?
ついに12月からランクマッチが解禁され、私もレイドバトルやオンライン対戦など
パルデア地方の冒険を楽しんでいます。

ところで皆さん、ポケモンで対戦しているときに困ったことはないですか?

あれ…自分と相手のポケモン、どっちのすばやさが高いんだ…?

あるある。とてもわかります。
私もポケモン剣盾からオンライン対戦を始めて約3年になりますが、未だに対戦中に何度も検索してます。
ポケモンのわざを選ぶまでの持ち時間は60秒
その短い時間で様々な読み合いをするポケモンバトルでは、
すばやさをインターネッツで検索している時間が敗北に繋がると言っても過言ではないのです。

そこで今回は、私のようなポケモン対戦初心者~中級者向けに、
「Alexaに話しかけるとポケモンのすばやさを返してくれるアプリ」
を作ってみました。
スマートスピーカー自体が下火な感じですが、
去年のブラックフライデーセールにて3000円ほどで購入し、
我が家で日々健気に明日の天気を教えてくれる Amazon Echo Dot に、
Alexa Skillを自作して新たな力を授けます。

前段1:Alexaとは

Alexa とは

Amazonが出している音声アシスタントサービスのこと。

www.amazon.co.jp

Alexaスキルとは

Alexaの機能を拡張するアプリのようなもの。
開発用のSDKも出ていて、ユーザーが自作することもできます。

developer.amazon.com

Alexa Developer Console という開発者向けページも用意されていて、
開発、ビルド、テスト、ストアへの公開まで全てブラウザ上で出来ちゃいます。
今回はこちらを使って開発していきます。

developer.amazon.com

実はノーコードでもAlexaスキルが作れちゃう

簡単なクイズや朗読、ちょっとしたミニゲームであれば、
テンプレートに沿ってポチポチしていくだけでSkillが作れちゃう
Alexaブループリントというサービスも用意されてます blueprints.amazon.co.jp

前段2:ポケモンのすばやさとは

今回求めるポケモンのすばやさについて軽く触れます。
ポケモンのすばやさとは、どちらのポケモンが先に攻撃するかを決めるステータスです。
値が1違うだけで先行と後攻が変わるため、ポケモン対戦では非常に重要なステータスとなります。
ポケモンのステータス(実数値)を算出する計算式は

能力値=(種族値×2+個体値+努力値÷4)×レベル÷100+レベル+10

式から分かるように、ステータスは
・ポケモン毎の特徴(種族値、個体値)
・トレーナーの育て方(努力値)
で大きく変わります。人間と一緒ですね。
今回はポケモンの種族値に加え、対戦環境でよく使われる
・最速(個体値V+努力値MAX振り+性格補正)
・準速(個体値V+努力値MAX振り)
・無振り(個体値Vのみ)
の値を返してくれる機能を作っていきます。
本記事では、計算に使われる各値についての詳しい説明は割愛します。本当は話したい。
ポケモン徹底攻略さんの説明がわかりやすいです。興味ある方はぜひ。

yakkun.com

本題:実装

ようやく本題。
公式のスキル作成のトレーニングを見つつ、HelloWorldのサンプルプロジェクトをあれこれ弄っていきます。
※今回はNode.jsで作ります(Pythonでも作れます)

スキルの呼び出し名(Invocation name)

Developer Consoleからスキルの呼び出し名を決めます。
「Alexa、〇〇を開いて」でスキルを呼び出す事が出来るようになります。

今回は「すばやさ博士」を人の手で作っていきます

インテント(Intent)の作成

インテントを作成します。(左メニュー > 対話モデル > インテント)
インテントとは、そのスキルに対するユーザーの意図、もしくは実行できるアクションの定義のようなものです
サンプル発話に「ユーザーがこうやって話しかける」というデータを登録します。

サンプル発話の登録
{PokemonName}にはポケモンの名前が入ります。(Alexaではスロットと言います。)
このスロットを変数として、メッセージの判定処理に利用します。

カスタムスロットタイプの設定

スロットの定義を行います。(左メニュー > アセット > スロットタイプ)
今回はポケモンの名前を{PokemonName}というスロットで取り扱うため、
どういった値が{PokemonName}に入ってくるか、スロットの定義を行います。
パルデア地方のポケモン400匹(フォルム違いなども含め合計406)の名前を登録しました。

CSV一括登録で楽ちん

コーディング

コードエディタタブから、コーディングしていきます。
今回はNode.jsで書きますが、書く量は少なく、Node.js詳しくないよって人でも大丈夫です。

DeveloperConsoleのコードエディタ画面。便利。

LaunchRequestHandler

LaunchRequestHandler はスキル呼び出しにキックされるイベントです。

const LaunchRequestHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
    },
    async handle(handlerInput) {
        const speakOutput = '私はすばやさ博士です。どのポケモンのすばやさについて知りたいですか?';
        return handlerInput.responseBuilder
            .speak(speakOutput)
            .reprompt(speakOutput)
            .getResponse();
    }
};

PokemonNameIntentHandler(メイン処理)

次にメインの処理である、PokemonNameIntentがキックされた時の処理を書きます。
今回は時間の都合上、DynamoDBやS3に保存したポケモンのデータを引っ張ってくる、といった
ところまで作れなかったので、speedDataという連想配列を内部で定義してます。
パラメータとして受け取ったポケモンの名前から種族値を検索→各値を計算して返す、という流れです。
今回は、オンラインの対戦環境を考慮して以下のルールで計算してます。
・個体値は最大値の31
・準速以上の努力値は実数値として効果のある最大値252で計算
・ランクマッチ対戦ではポケモンのレベルは50固定のため、50で計算
※努力値は252÷4=63で計算してます。性格補正は1.1倍です。

const PokemonNameIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'PokemonNameIntent';
    },
    handle(handlerInput) {
        let speakOutput = `パルデア地方にはいないポケモンのようです`;
        const pokemonName   = handlerInput.requestEnvelope.request.intent.slots.PokemonName.value;
        if(pokemonName in speedData){
            const raceValue     = speedData[pokemonName]; // 種族値
            const defaultSpeed  = parseInt((raceValue * 2 + 31) * 50 / 100 + 5); // 無振り
            const secondSpeed   = parseInt((raceValue * 2 + 31 + 63) * 50 / 100 + 5); // 準速
            const topSpeed      = parseInt(secondSpeed * 1.1); // 最速
            
            speakOutput = `${pokemonName} のすばやさ種族値は ${raceValue} 。実数値は、最速 ${topSpeed}。準速 ${secondSpeed} 。無振り ${defaultSpeed} です。`;
        }

        return handlerInput.responseBuilder
            .speak(speakOutput)
            .reprompt(speakOutput)
            .getResponse();
    }
};

IntentHandlerのインポート

index.jsの下部にIntentHandlerのインポート箇所があるため、忘れずに追加しておきましょう。

exports.handler = Alexa.SkillBuilders.custom()
    .addRequestHandlers(
        LaunchRequestHandler,
        //HelloWorldIntentHandler,
        PokemonNameIntentHandler, // 追加
        HelpIntentHandler,
        CancelAndStopIntentHandler,
        FallbackIntentHandler,
        SessionEndedRequestHandler,
        IntentReflectorHandler)
    .addErrorHandlers(
        ErrorHandler)
    .withCustomUserAgent('sample/hello-world/v1.2')
    .lambda();

テスト!

ここまで出来たら「コードエディタ」タブ内のデプロイボタンを押しましょう!
AWS Lambdaの関数として、いい感じに色々やってくれます。
AWS Lambdaは月100万リクエストまで無料なので、お財布にも優しいですね。

実装が出来たので、いよいよテストです。

Developer Consoleでのテスト

なんとブラウザ上でテストまで出来ちゃいます。
PCにマイクを繋げば音声入力でのチェックも出来るので、実機が無い場合でも安心です。

Alexaの音声も再生してくれます

いざ実機

チュートリアルの案内に沿って、Web版Alexaアプリから、開発したSkillが登録されているかチェックします。

WEB版Alexaアプリの表示。チュートリアル記載のスキルメニューがない。

このウェブサイトは、すべてのAmazonデバイスとAlexaの機能に対応しているわけではありません。機能はこれからも引き続き削減されます。すべての機能にアクセスするには、iOSアプリストアまたはGoogle Playストアから最新バージョンのAlexaアプリをダウンロードしてください。

スキルの項目が無くなっている…。やっぱりスマートスピーカー関連サービスは縮小傾向なんでしょうか。
仕方がないのでiOS版のAlexaアプリをダウンロードし、確認します。

誰か説明文を変更する方法を教えてください

いよいよ実機確認です。

www.youtube.com

イヤッタアアアアアアアアア!!!
これでオンライン対戦が捗りますね。
スマホでダメージ計算しながらすばやさ確認ができる(´ε` )

感想

今回思い切ってAdvent Calendarに参加しましたが、
最近は業務でコーディングをする機会がめっぽう減っており、
ガチャガチャと何かを作るのは楽しい!という気持ちを再確認する良い機会となりました。
それでは皆さま、良きポケモンライフを!

余談

余談その1

今回の開発中にたまたま知ったんですが、
実はAlexaにはデフォルトでポケモンの種族値を返す機能が実装されてるみたいです。
剣盾までのポケモンしか登録されてないようですが、
「Alexa、マルマインのすばやさを教えて」
といった感じで話しかけると種族値を教えてくれます。便利すぎるぜAlexa。。。

余談その2

以前、弊社CTOの駒田さんに貰ったGoogle Nest Miniを使って何かアプリを作りたかったけど、
Action on Google(GoogleHomeのアプリ作るサービス)が2023年6月に終了するとのことで断念しました。

悲しみのGoogleNestMiniくん(供養)