Go
数学
bot
Slack
GoogleCloudPlatform

素数大富豪がはかどる素数判定Botをビルドしてみた(Slack 編)

More than 1 year has passed since last update.

この記事は、社内有志数名による第2回素数大富豪を数日後に控え、前回使用した素数判定アプリが iOS 11 ではもはや動作しないことが判明したため、素数判定してくれる Bot を(ほかのアプリを探してもよかったのですがせっかくなので)ビルドしたメモです。


ゴール


  • 与えられた数を素数判定できる。

  • 判定履歴を残せて共有もできる。


完成イメージ


completed-app-image.png


スタック

単なる思いつきと多少の事情から今回採用したスタック。

image.png


実装

ここでは、GCP にプロジェクトID prime-daifugoが存在する前提で話を進めます。プロジェクトIDは任意ですので、ご自身のプロジェクトIDに読み替えて進めてください。

実装の手順はつぎの通りです。


  1. App Engine を作成

  2. 素数判定アプリをデプロイ

  3. API key を発行

  4. Dialogflow にエージェントを作成

  5. Cloud Functions を作成

  6. Dialogflow と Slack App のインテグレーション


1. App Engine を作成

gcloud コマンドラインツールを使って、ターミナルからつぎのように実行します。

$ gcloud components update

$ gcloud auth login
$ gcloud config set project prime-daifugo
$ gcloud config list
$ gcloud app create --project=prime-daifugo --region=asia-northeast1

素数判定 API バックエンドとして利用するアプリのサンプルコードを用意しました。よろしければ、リポジトリを clone してお使いください。

肝心の素数判定には、src/math/big/prime.goProbablyPrime を使用しています。

この関数のコメントにもあるように、2の64乗(18446744073709551616)より小さい数であれば、100% 正確に素数判定できるようですので、素数大富豪で採用する分には実用上の問題はなさそうです。

$ git clone https://github.com/azukiwasher/primality-testing-apps

$ cd primality-testing-apps/go

primality-testing-apps/go/openapi-appengine.yaml をエディタで開いて YOUR-PROJECT-ID を "prime-daifugo" に変更します。

# [START swagger]

swagger: "2.0"
info:
description: "A primality test API to determine whether an input number is prime."
title: "Primality Test API"
version: "1.0.0"
host: "YOUR-PROJECT-ID.appspot.com"
# [END swagger]

snip...


2. 素数判定アプリをデプロイ

まず、Cloud Endpoints をデプロイします。

$ gcloud endpoints services deploy openapi-appengine.yaml

$ gcloud endpoints configs list --service=prime-daifugo.appspot.com
CONFIG_ID SERVICE_NAME
2017-12-11r0 prime-daifugo.appspot.com

primality-testing-apps/go/app.yaml をエディタで開いて YOUR-PROJECT-ID を "prime-daifugo" に、CONFIG_ID を "2017-12-11r0" にそれぞれ置き換えます。

snip...

endpoints_api_service:
# The following values are to be replaced by information from the output of
# 'gcloud service-management deploy openapi.yaml' command.
name: YOUR-PROJECT-ID
config_id: CONFIG_ID

snip...

最後に、アプリをデプロイします。

$ gcloud app deploy


3. API key を発行

GCP コンソールの左メニューから APIs & Services をクリックして、API key を発行します。Cloud Functions から Cloud Endpoints へリクエストする際の認証トークンとして使用します。

publish-api-key.png

credentials.png

App Engine へのアプリのデプロイと API key の発行が済んだので、API バックエンドの動作をテストしてみましょう。

ENDPOINTS_KEY にさきほど発行した API Key を設定します。 ENDPOINTS_HOST には "prime-daifugo.appspot.com" を設定します。

$ export ENDPOINTS_KEY=YOUR-API-KEY

$ export ENDPOINTS_HOST=https://YOUR-PROJECT-ID.appspot.com
$ curl -X GET -H "content-type:application/json" "${ENDPOINTS_HOST}/primes/1213?key=${ENDPOINTS_KEY}"
{"number":1213,"is_prime":true}
$ curl -X GET -H "content-type:application/json" "${ENDPOINTS_HOST}/primes/1729?key=${ENDPOINTS_KEY}"
{"number":1729,"is_prime":false}


4. Dialogflow にエージェントを作成

Dialogflow コンソールにログインして、エージェントを作成します。

agent.png

エージェントの属性は、たとえばつぎのような感じで設定します。入力が済んだら CREATE をクリックします。

Key
Value

Agent name
Rachael

DEFAULT LANGUAGE
Japanese - ja

DEFAULT TIME ZONE
(GMT+9:00) Asia/Tokyo

GOOGLE PROJECT
prime-daifugo

エージェントを作成したら、Intents を登録していきます。Intents は、ユーザから送信されるメッセージとエージェントのアクションとのマッピングを表現します。

intents.png

CREATE INTENT をクリックして、つぎのように Intents を入力します。

Key
Value

Intent name
素数判定

User says
2は素数ですか?

User says に「2は素数ですか」と入力します。「2」の上にイエローのマーカーが掛かるはずです。そこをクリックするとオプションが表示されるので、下にスクロールして @sys.number を選択します。

この @sys.number は、Entities と呼ばれるディクショナリに含まれる定義のひとつで、ユーザから送られるメッセージから特定の値(parameter values)を抜き出すために参照されるものです。抜き出された値は、のちほどふれる Cloud Functions へ渡す引数として利用できます。

user-says.png

Actionセクションにレコードがひとつ追加されます。左端にあるREQUIREDをチェックします。

action.png

右端に PROMPTS 列が現れます。Define prompts... をクリックして、つぎのような感じでユーザからのメッセージに数字が含まれていない場合の返答を用意しておきます。

prompts.png

テストします。右上にある Try it now から数字を含む適当なセンテンスを入力してみます。 INTENT が「素数判定」、VALUE が「1213」となればOKです。 いまのところ、DEFAULT RESPONSENot available で問題ありません。

test-intent.png

最後に、SAVE をクリックします。

つぎから、GCP の Cloud Fucntions と Dialogflow の Fulfillment を利用してレスポンスを返すように仕込んでいきます。


5. Cloud Functions を作成

GCP コンソールに戻り、Cloud Functions メニューをクリックします。つぎのダイアログが表示されたら、Create function をクリックします。

create-cloud-functions.png

各フィールドに値を入力していきます。

image.png

たとえばこんな感じです。

Key
Value

Name
primality-test

Memory allocated
128 MB

Trigger
HTTP Trigger

Source code
Inline editor

index.js
*以下に示すソースコード

Storage bucket
prime-daifugo.appspot.com/

Function to execture
primalityTestWebhook

index.js にはつぎのコードを入力します。YOUR-HOST は "prime-daifugo.appspot.com" にします。 YOUR-API-KEY4. API key の発行 で発行した Key を設定します。

/*

* HTTP Cloud Function.
*
* @param {Object} req Cloud Function request context.
* @param {Object} res Cloud Function response context.
*/

'use strict';
const https = require('https');
const host = 'YOUR-HOST';
const apiKey = 'YOUR-API-KEY';

exports.primalityTestWebhook = (req, res) => {
let number = req.body.result.parameters['number'];
callPrimalityTestApi(number).then((output) => {
// Return the results of the primality test API to Dialogflow
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify({ 'speech': output, 'displayText': output }));
}).catch((error) => {
// If there is an error let the user know
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify({ 'speech': error, 'displayText': error }));
});
};

function callPrimalityTestApi (number) {
return new Promise((resolve, reject) => {
let path = '/primes/' + number + '?key=' + apiKey;
console.log('API Request: ' + host + path);
// Make the HTTP request to get the result of test
https.get({host: host, path: path}, (res) => {
let body = ''; // var to store the response chunks
let message = '';
res.on('data', (d) => { body += d; }); // store each response chunk
res.on('end', () => {
// After all the data has been received parse the JSON for desired data
let response = JSON.parse(body);
let output = response['is_prime'];
console.log(output);
if (output) {
message = "素数ね。";
} else {
message = "素数じゃないわ。";
}
resolve(message);
});
res.on('error', (error) => {
reject(error);
});
});
});
}

ポイントとしては、let number = req.body.result.parameters['number'] で、さきほど Dialogflow の Intents で設定した @sys.number で抜き出される値、つまり数値が渡されています。

入力がすべて完了したら、Create をクリックします。Overview から "primality-test" をクリックして、Trigger メニューを選択します。

cloud-functions-trigger.png

URL に表示されているアドレス https://us-central1-prime-daifugo.cloudfunctions.net/primality-test を控えておきます。Dialogflow の Fulfillment の設定で必要になります。

functions-url.png

Dialogflow コンソールに戻り、Fulfillment を選択します。Fulfillment は、ユーザから送られるメッセージにマッチした Intent(素数判定)から値(@sys.number:number)を受け取り、webサービス(Cloud Functions)を呼び出してその結果を返してくれる、いわゆる webhook を実現するための仕組みです。

WebhookENABLED にして、Cloud Functions で控えた URL を URL* に入力し、SAVE をクリックします。

fulfillment-url.png

Intents の「素数判定」から、画面中央下にある Fulfillment をクリックして Use Webhook をチェックします。最後に SAVE をクリックします。

fulfillment-webhook.png

Webhook の動作をテストしておきましょう。Try it now にセンテンスを入力して、DEFAULT RESPONSE にメッセージが返えされれば、OKです。

test-webhook.png


6. Dialogflow と Slack App のインテグレーション

Slack とインテグレーションするためのとてもわかりやすいガイドが用意されています。これにしたがって Slack App をセットアップして、ここで作成した Dialogflow エージェントとインテグレーションしてみてください。

うまくインテグレーションできれば、こんな感じのやりとりが Slack 上で実現できるはずです。

rachael-primality-testing-bot.gif


まとめ


実戦投入してみた感想

実際に素数大富豪で使用してみると、確かに、判定履歴は次回以降に向けた傾向と対策を模索するのに有益な情報なのですが、合成数出しをはじめ、2JQK といったメジャーどころはわざわざ判定にかけないので対戦記録(棋譜)としては不十分なものになってしまいました。

当初は、Slack に専用チャネルをひとつ作って、プレーヤーが自分の出した札を自分のスマホから Bot に判定させれば、だれがいつ何を出したのか履歴も勝手に残るので効率的だと考えていましたが、逆に、過去に出された素数も含めてお互いがシェアしてしまうと、あらたな素数との出会いが少なくなるといった懸念もあり、そのような運用は今回は見送りました。


今後の課題

判定した数が素数でなかった場合は、素因数分解の結果を返してあげるとプレーヤーの納得感が高まりそうです。また、すべて数字ではなく、J Q K を直接入力値に含められると(Joker を含む場合はむつかしそうですが)、出したカードの枚数も分かってよさそうです。

今回のような使い方では、素数判定した結果がプレイヤー全員に見えるようスマホを一台、テーブルの上に置くことになり、毎回毎回前屈みになってスマホに向かって音声入力(またはタイプ)しなければならず少々ツラかったです。

Dialogflow であれば、Google Assistant とのインテグレーションも可能なので、Google Home を導入すれば、履歴は履歴で残しておき、グラス片手にエレガントな姿勢のまま、これまでにない腰痛フリーなゲーム進行が可能になるかもしれません。

また、場所柄差し支えなければ、各プレイヤーのターンで残り時間をカウントしたり、雰囲気を演出したければ Spotify から素敵なサウンドを流しておき、小腹が減ったらピザを注文(まだ無理?)するなど、素数大富豪における Google Home のポテンシャルは案外高そうです。

最後に、蛇足ですが、App Engine に API バックエンドをたてるのではなく、Cloud Functions の index.jsで素数判定してしまえば、App Engine の費用が浮きます。今回は、Go で素数判定する方法を調べたかったのであえてこのようなスタックになってしまいました。ふだんは、App Engine インスタンスを Disabled にしておき、素数大富豪を楽しむときだけ、App Engine > Settings から Enable application しましょう。

以上です。最後まで読んでいただきありがとうございます。