inosyanのブログ

プログラム、家庭菜園、僕が興味を持ったことを書いていきます

菜園を始めた理由

僕はプログラマーですが、仕事の傍ら週末は菜園を手入れしています。 去年のゴールデンウィークからはじめたので、ちょうど1年が経過したところです。

今週末はきゅうりの手入れとダイコンの間引き、近々定植するトマトのために支柱を立てたりしました。

にんじんは去年は失敗したけど、今年はいまのところ順調です。 去年は秋ごろに植えて、もう寒くなりかけていたのと、発芽前の水をきらしちゃいけない時期に数日畑に行けなかった時期があったので、それでうまくいかなかったのですが、今はまあまあ温かいし、水もしっかりあげているので、今年こそは大丈夫。

僕が農業に興味を持ったのは去年の冬、NHKで「2030 未来への分岐点」という番組を見たのがきっかけでした。食肉を輸入するとその生産国の水資源も使っているということになり、今それが枯渇しつつあるということを知り、衝撃をうけました。 近い将来お肉は食べられなくなるかもしれません。動物を育てるには大量の穀物が必要で、動物に与えるより人間が直接それを食べたほうが食料供給の効率が良いからです。

肉好きの僕としてはとても不安になりました。
いろいろ調べていく中で興味深い資料を見つけました。

農林水産省知ってる?日本の食糧事情

いざという時、芋類を中心とした食生活に切り替え必要カロリーをまかなうシミュレーションに驚きました。 日本の食糧事情は脆弱です。お肉が食べられないどころの話ではありません。

食料が不足しはじめると当然値段があがり、一部のお金持ち以外は飢えることになるでしょう。
わずかな食べ物をめぐって熾烈な争奪戦になるでしょう。
僕はお金持ちでもないし、人をおしのけて戦える自信もありません。

SDGsでいい方向に向かおうという時に戦争がはじまってしまうし、今の世界の状況をみると悲惨なシナリオが現実味をおびて見えてきます。

「だったら自分で食料を作りたい」そう思いました。いざとなったら自給自足できるようになりたいと。 でも知識も経験もないのに、いきなりは無理です。

なにか簡単なことからはじめたいと思い、最初にみつけたのが「水耕栽培」でした。
土を使う農業と違い、水耕栽培では環境を人間がコントロールしやすいので、これなら簡単にできそうな気がしました。

ホームハイポニカというキットを使って机の下でレタスを育てることに成功しました。

それから、土を使った農業も知っておきたいと思い、自治体から近所の菜園を借りることにしました。
広さは 4 x 6m = 24㎡
農家のとは比べ物にならないほど狭い畑ですが、机の下の水耕栽培よりはとてつもなく広く、収穫も経験もはるかに多く得ることが出来ました。

土はコントロールしづらい厄介なものというイメージでしたが、最近では土作りにも興味が湧いてきました。

はじめは危機感からはじめた園芸ですが、だんだん面白くなってきました。
お日様の光をあびながら農作業をするのは気持ちがいいし、収穫があるとうれしくなります。枯れてしまったり失敗すると悲しい気持ちにもなりますが、それも含めて楽しんでいます。

playdateを注文したけど届くのずっと先なのでゲームを作って待つことにした

playdateがついに発売された

以前から気になっていたガジェット「playdate」がついに発売されました。
小さくて画面はモノクロ、十字キーにABボタン、初代のゲームボーイのような見た目ですが、手回しのハンドルがついている風変わりなゲーム機です。

開発は「いたずらガチョウがやって来た」のゲームで有名なPanicさん。このゲームはプレイしたことはないけどYoutubeでプレイ動画を見て家族で和んでました。面白いものを作ってくれました。

ゲーム自体はカセットを買って差し込むわけではなく、はじめの12週間は毎週2本ずつ新しいゲームを受け取れるようです。Wi-Fiがついているのでおそらくそれ経由でインストールされるのだと思います。
その後のことは今準備中とのことですが、僕が一番このガジェットで気に入っているのは「ゲームを自分で作れる」ことです。
開発者向けサイトからSDKを手に入れれば、ゲームを自分で作ることができ、また作ったゲームをシェアすることができます。

このガジェットを見つけたのは数年前だったけど、発売が延期になり忘れてました。ですが最近出荷が開始された記事を読んで、また興味が湧いてきました。
でも、買ってもすぐに飽きて使わなくなってしまうのは良く無いので、買う前に簡単なゲームを作ってみて面白いかどうか試してみることにしました。

ゲーム開発はいくつか方法があります。

  1. pulp で作る
  2. Lua で書く
  3. C言語 で書く

はじめは簡単そうなpulpを試しました。これはブラウザ上で動く開発環境で、プログラム以外にもグラフィックや音楽も作れるかなり強力なツールです。最初に触ってみて雰囲気を掴むにはいいと思います。
キャラクターを操作してフィールドを歩きまわるような初代ゼルダの伝説のようなゲームはこれで作れるかもしれません。
一番最初のゲームはこれで作ってみるつもりでしたが、pulpが想定した形のゲームじゃないと難しいことがわかりました。(例えば、必ずプレイヤーが操作するプレイヤーが1人画面上にいなければならないなど)

最初にpulpを試してみた

次に、Lua向けのSDKをダウンロードしました。C言語のほうがパフォーマンスが出るようですが、まだそこまでは求めてないし、サクッと簡単に開発できそうなほうにしました。
Luaは触るのはほぼ初めてです。昔Ultima Onlineをやってた時、Luaでアプリをカスタマイズできるというのを聞いて手を出そうとしたのですが、その時はうまくいきませんでした。Lua再入門です。

と言っても、ググりながら書けば問題ないです。array と table が同じなのはいいけど、indexが1から始まるのには面食らいましたw

教材はSDKについてるサンプルコードとリファレンスです。
サンプルコードはよくできています。でも読んでる時間が惜しいので目ぼしいところだけ参考に自分で書きました。

(今見てみたら、チュートリアルがありました。前からあったのかな?)

リファレンスが頼みの綱ですが、正直ちょっと分かりにくいです。
一つ一つはわかるけど、全体のつながりがわかりにくい。サウンド関連がたくさんありすぎて、どれを使ったらいいのかはじめわかりませんでした。(今もたぶんわかってないです)
とりあえず、MIDIデータを track につっこんで、それを sequence につっこんでplayすれば音が鳴るのでそれでBGMもSEもまかなってます。

例えばこんな感じです。JSONMIDIデータを書いてます。step は、タイムラインのフレームのようなもので、デフォルトでは 16step/秒 です。noteはドレミです。72はC5、つまり5オクターブのドの音です。

synth = playdate.sound.synth.new()
track = playdate.sound.track.new()
dat = '[{"note":76,"length":1,"velocity":1,"step":3},{"note":74,"length":1,"velocity":1,"step":2},{"note":72,"length":1,"velocity":1,"step":1}]'
track:setNotes(json.decode(dat))
track:setInstrument(synth)
seq:addTrack(track)

seq:play()

mp3やwavが扱えるので、普通はガレージバンド等で作って読み込んだ方が楽なんでしょうが、せっかくレトロ風なガジェットなのでファミコン風の音を鳴らしたいし(ガレージバンドでも8bit音源で出来ますが)、プログラムで音を作るのも楽しそうなのでそうしました。
あまり凝ったことができない分、割り切って開発できるのでそれはそれでいいです。ガレージバンドが面白くなっちゃうといつまでもゲームが完成しないのでw

ただ、このJSONを作るのは、手打ちだとやりにくかったので、Google Spreadsheet に打ち込んで Apps Script を書いて JSON を書き出すようにしました。

google spreadsheets での音データ打ち込みの様子

Apps Script の画面

実行するとクリップボードにコピーされるので、それをコードに貼り付けます。

出来上がったJSONクリップボードにコピー

Code.gs

function main() {
  var html = HtmlService.createTemplateFromFile("dialog").evaluate();
  SpreadsheetApp.getUi().showModalDialog(html, 'Playdate MIDI Data Maker');
}

function calc() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var values = sheet.getDataRange().getValues();

  var idxS = 0, idxE = 0;
  for (var i = 0; i < values[1].length; i++) {
    if (values[1][i] === 'S') {
      idxS = i;
    }
    else if (values[1][i] === 'E') {
      idxE = i;
    }
  }

  var noteList = [];
  for (var i = 2; i < values.length; i++) {
    var noteName = values[i][2];
    if (noteName === '') continue;
    var noteData = null;
    for (var j = idxS; j <= idxE; j++) {
      if (values[i][j] !== '') {
        if (noteData === null) {
          noteData = {
            note: noteName,
            length: 0,
            velocity: 1,
            step: j - idxS + 1
          }
        }
        noteData.length++;
      }
      if ((values[i][j] === '' || j === idxE) && noteData !== null) {
        noteList.push(noteData);
        noteData = null;
      }
    }
  }
  var json = JSON.stringify(noteList);
  
  console.log(json);
  return json;

dialog.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <button onclick="onCopyButtton()">Copy to Clipboard</button>

    <script type='text/javascript'>
      function onCopyButtton() {
        var content = <?= calc(); ?>;
        console.log('content', content);
        navigator.clipboard.writeText(content)
          .then(() => {
            console.log("Text copied to clipboard...")
        })
          .catch(err => {
            console.log('Something went wrong', err);
        })
      }
    </script>

  </body>
</html>

あと、コードエディタですが、nova を使っています。これはpanicが開発したアプリで、playdate専用というわけではないようですが、playdateのシミュレーターと連携しているので開発しやすそうです。
ただ、有料ですw
初回は ¥11,800で、その後は1年ごとに¥5,900/年だそうです。
今は30日間のお試し期間中なので、終わったらどうするか考えます。VSCode用の機能拡張もあるようなので、後日それも試してみます。

なんとか1個目のマインスイーパー的な(ほぼそれw)ゲームができました。

完成したゲーム「Bomb Panic」
プレイ動画

そして注文しました。
年末に発送の予定ですが、あまり期待せず来年くるつもりで待っています。
それまで何本つくれるかな?

良かったこと

  • モノクロのドット絵なので、ポチポチ書くだけでまあそれなりの見た目になる
  • 懲りたくても限界があるので、いい意味で割り切って作業できる。つまり内容に集中できる
  • 大作を作ろうなんて気負わず、サクッと作れるのでいい気晴らしになる
  • Luaにclassはないけどclass風なことはできるので、たいていなんとかなりそう

次は何を作ろうか

3年ぶりに投稿します

久しぶりの投稿です。前回が2018年12月だったので3年以上空けたことになります。
エンジニアとして世の中に情報をアウトプットしていく決意のもと、それまでTwitterFacebookもそれほど利用せず積極的に何かを発信したことのない僕が、少し無理して物書きをしてきたわけですが、約一年続けた時点で、年末に一旦区切りをつけました。

そして3年経った今、いろんなことが変わりました。

電車通勤しなくてよくなったのは大きいですね。エンジニアとして使える時間が多くなりました。会議もリモートなので、続けて別の会議があっても移動時間は0分ですw

前はUnityとかネイティブアプリでしたが、FlutterやWebの案件が多くなりました。WebもjQueryではなくReactとかVueですね。

使っているモニタもでかくなりました。当時は15inchのmacに23inchの外部モニタだったような気がしますが、今は16inchのmacに41inchのモニタですw

そして、これが当時の自分が聞いたらびっくりするでしょうが、貸し農園を借りて家庭菜園をしています。
面白そうだとか前から興味があったとかそんな理由ではなく、ある日突然今すぐやらなきゃいけない気がして、それで始めました。
詳しくはまた後で。

人とも会わず一人黙々といろんなことに手を出していますが、そこで得た知識や工夫、成功や失敗などは隠してくようなことでも無いので、また書くことにしました。

以前はプログラム関連の記事を書いていましたが、これからは家庭菜園関連のことや興味のあることをすべて書いていこうと思います。
書くことで、自分が何をしたいのか見つめ直したいです。

語学学習サービス「Duolingo」の正誤判定がすごく優秀な件

f:id:inosyan:20181222235321p:plain

 
   いまからひと月ほど前 Duolingo というサービスを知り、そこで毎日英語を勉強しています。英語は僕にとっては長年克服できないでいる壁で、通勤電車の中で英単語を勉強しているのですが、それだけだと足りない気がしていた中、良いものを見つけました。

 
 

Duolingoとは

サイトには、

Duolingoは、世界で最も利用者数の多い無料オンライン語学学習プラットフォーム

とあります。このサービスは基本無料で、PCでもスマホでも受けられるサービスなので気軽にはじめられます。

 
 

英語に限らずいろんな言語を習得できる

 Duolongoのユーザーは、英語を勉強したい日本人だけではありません。Duolingoには、いろんな母国語の人が様々な言語を習得するためのコースが用意されています。

f:id:inosyan:20181222235430p:plain
 
 例えば英語が母国語の人なら、スペイン語、フランス語、ドイツ語、日本語などの中から学習したい言語を選ぶことができます。

f:id:inosyan:20181222235516p:plain

 
 
 しかし、残念ながら、日本語が母国語の人は英語しか選択肢がありません。このサービスが他の言語にも対応してくれると嬉しいのですが、英語をマスターすれば英語話者用のコースから他の言語を勉強すれば良いので問題ありませんw

f:id:inosyan:20181222235535p:plain

 
 

目標がわかりやすい

 ホーム画面は学習項目のアイコンが並んでいるだけのシンプルなものです。下にスクロールしていくと、フクロウ(名前はDuo)のトロフィーがあるので、ひとつずつクリアしていって最後までたどりつくのが目標なんだということがわかります。アイコンの上に数字が書いてありますが、これは学習レベルで、はじめは1からスタートし、規定量のレッスンを終えると次のレベルに上がります。

f:id:inosyan:20181222235557p:plain

 
 

はじめは飛び級テストをうけてガンガンに進もう

 下にスクロールするほど難しくなるようで、学習項目がチェックポイントで区切られています。レベル1の時は順番に項目をクリアしていかないと、次のチェックポイントより先の項目が選べないようになっています。しかし、チェックポイントは「飛び級テスト」をうけることでクリアできます。もし簡単すぎるようなら飛び級してどんどん先に進みましょう。

f:id:inosyan:20181222235615p:plain

 
 
 学習をはじめたばかりの頃、項目を全部クリアして最後のフクロウのところまでたどり着けば終わりだと思っていました。ですがおわりではなく次のレベルがありました。すごい量のコンテンツが用意されているようですね。上から順に下までいくのを繰り返して、いまは3周目(レベル3)にチャレンジ中です。

f:id:inosyan:20181222235632p:plain

 
 

正誤判定がすばらしい

 このサービスで僕が一番すごいと思ったのは、判定能力のすごいところです。例えばこの問題

f:id:inosyan:20181222235653p:plain

The green animal eats the white bread.

の日本語訳に対し、僕は

その緑色の動物は白いパンを食べます

と書いて正解しましたが、別の正解例として

f:id:inosyan:20181222235709p:plain

その緑色の動物はその白いパンを食べます。

が提示されました。僕の回答には「白いパン」の前に「その」がないけど正解だと判定してくれています。翻訳の正解はひとつではないので、ユーザーが自由に入力する文章は様々なパターンがありますが、それらを正誤判定するには、文字列の完全一致ではなく文章として解析しなければなりません。プログラムでそれを行うのは大変ですが、それができているところがすごいです。(もしかしたら、ものすごい数の正解文例をデータベースに持っている可能性もあり、システム的にもそっちのほうが作りやすい気もしますが、それはそれですごいです)

 また、別の問題で
f:id:inosyan:20181222235738p:plain

彼女は自分は元気だと書きます。

の英語訳に対し、僕は

She writes she is fine.

と書いて正解しましたが、別の正解例として

f:id:inosyan:20181222235752p:plain

She writes that she is fine.

が提示されました。僕の回答には that がなかったけど正解にしてくれました。

 上記の例では、僕の回答と正解例との間にはちょっとした違いしかありませんでしたが、
f:id:inosyan:20181222235808p:plain

彼女が眠るといつでも私は遊ぶ。

の英語訳に対し、僕は

I play everytime she sleeps.

と書いて正解しました。しかし別の正解例として

f:id:inosyan:20181222235828p:plain

Whenever she sleeps, I play.

が提示されました。僕が入力した文章とは全く異なりますし、しかも「スペースを入れ忘れています」と指摘されています。

 僕の入力した「everytime」は間違いで「every time」が正解です。スペルミスは1箇所だけなら正解にしてくれるようで、しかも正解例と全く異なる言い回しでも正しければ正解としてくれるところが素晴らしいです。

 
 

でもいつも正しく判定してくれるわけではない

 正誤判定は素晴らしいのですが、それでもたまに納得のいかない判定のときもあります。

f:id:inosyan:20181222235845p:plain

Where are you?

の日本語訳に対し、僕は

あなたはどこですか?

と書いて不正解になり、正しい訳として

f:id:inosyan:20181222235903p:plain

どこいるの?

が提示されました。自分の回答も間違ってないと思うのですが、そんなときは「会話する」を見てみましょう。そこはユーザー同士が意見を言い合う場所で、他のユーザーから参考になる意見が聞けます。僕以外にもこの正解例に不満を持っている人がいるようです。

f:id:inosyan:20181222235921p:plain

 サービスの向上のため、レポートすることもできます。もし自分の回答が正しいと思うなら「私の答えは正しいはずです」にチェックを入れて送信しましょう。

f:id:inosyan:20181222235938p:plain

 
 

スマホ版もあるけどPC版がおすすめ

 DuolingoはPCのブラウザでも、スマホのアプリでもでき、学習の進捗は同期されるので両方を使うこともできます。UIもほぼ同じですが、すこしだけ違いがあります。

 回答の入力方法ですが、PC版はキーボードを使って入力する方法と、単語帳から選択する方法のどちらかを選ぶことができますが、スマホ版は単語帳から選択する方法しか選べません。

 

キーボード入力
f:id:inosyan:20181222235956p:plain

 
単語帳から選択
f:id:inosyan:20181223000016p:plain

 
 スマホはPCより文字を入力しづらいので仕方ないのかもしれません。しかし単語帳から選ぶ方式では、単語がヒントになってしまうので、ゼロから文章を考えるより簡単になってしまいます。それに、タイピングが早ければキーボードで入力したほうが早く、学習中はキーボードだけですべての操作ができるので、スピーディーに進めることができます。

 ですが、スマホ版には発音をチェックしてくれる機能があります。この機能はPC版にはありません。理由はわかりませんが、PCにはマイクがついてないものや、周りに人がいる場合があるので、そのことを配慮しているからしょうか。

 人前でのスピーキングするのを恥ずかしがる日本人は多いと思います。スマホなら一人になれる場所にいけるけど、PCだと必ずしもそれが可能ではないですからね。でも、ぜひPC版でもこの機能をつけてもらいたいです。マイクがない人や、スピーキングをしたくない人のためにOn/Offを切り替えれるようにしてあれば問題ないと思います。

f:id:inosyan:20181223000037p:plain

 
 

継続させる仕組み

 そもそも僕は好きで英語を勉強しているので、動機づけをしてもらわなくても続けることはできると思います。でも、連続して学習した日数が増えていくのは楽しみです。炎のアイコンがそれで、いま29日間継続中です。

f:id:inosyan:20181223000055p:plain

 学習を開始して以来、まだ一度も学習を休んだ日は無いのですが、もし一日あけてしまうと0になってしまうのだろうと思います。0に戻ってしまうのはもったいない気がするので、毎日やるようにしてます。

 もうひとつの動機づけとして、リンゴットというDuolingo内の通貨があり、学習によってそれが増えていきます。これは僕にとってはそんなにモチベーションにはなっていません。というのも、リンゴットでできることがあまりないからです。PC版では、リンゴットを賭けて7日間連続学習できたら倍にする「ダブル・オア・ナッシング」や、制限時間付きで学習できるようになる「タイマー付き練習」などが買えるようです。スマホ版ではそれ以外に、フクロウのキャラクター「Duo」のきせかえアイテムを買えるようです。なぜPC版にないのかは不思議ですが、スマホ版のほうが若い人を意識した作りになっている気がします。

 とにかく英語はコツコツ毎日やる事が大事なので、連続日数が途絶えないように、あるかどうかわからない全クリアめざして頑張りたいと思います。

Googleアシスタント用アプリ「バトルゲーム」をリリースしてわかった開発の注意点

f:id:inosyan:20181123231403p:plain

 前回の記事「指輪型ガジェット「ORII」のBackerになった」で書いたように、音声アシスタントに興味がでてきたので、Googleアシスタント用のアプリを作ってみました。

inosyan.hateblo.jp

 こちらが実際に動いている様子です。

 興味を持ってからアプリを作ってリリースするまで、そんなに大変ではありませんでしたが、いくつか注意すべき点もあるので開発に興味を持った方のためにメモを残します。作り方の手順についてはいろんな方が説明されていますのでそちらをご覧ください。

 Actions on Googleで新規にプロジェクトを作り、Actionsでアクションを作るとDialogflowにエージェントが作られます。DialogflowにはFulfillmentという画面があり、そこがプログラムを書く場所です。そこの画面でInline EditorをEnableにすると、サンプルのプログラムを見ることができます。

f:id:inosyan:20181123231423p:plain

サンプルコードはこのようになっています。

// See https://github.com/dialogflow/dialogflow-fulfillment-nodejs
// for Dialogflow fulfillment library docs, samples, and to report issues
'use strict';
 
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const {Card, Suggestion} = require('dialogflow-fulfillment');
 
process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
 
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
  const agent = new WebhookClient({ request, response });
  console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
  console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
 
  function welcome(agent) {
    agent.add(`Welcome to my agent!`);
  }
 
  function fallback(agent) {
    agent.add(`I didn't understand`);
    agent.add(`I'm sorry, can you try again?`);
}

  // // Uncomment and edit to make your own intent handler
  // // uncomment `intentMap.set('your intent name here', yourFunctionHandler);`
  // // below to get this function to be run when a Dialogflow intent is matched
  // function yourFunctionHandler(agent) {
  //   agent.add(`This message is from Dialogflow's Cloud Functions for Firebase editor!`);
  //   agent.add(new Card({
  //       title: `Title: this is a card title`,
  //       imageUrl: 'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png',
  //       text: `This is the body text of a card.  You can even use line\n  breaks and emoji! 💁`,
  //       buttonText: 'This is a button',
  //       buttonUrl: 'https://assistant.google.com/'
  //     })
  //   );
  //   agent.add(new Suggestion(`Quick Reply`));
  //   agent.add(new Suggestion(`Suggestion`));
  //   agent.setContext({ name: 'weather', lifespan: 2, parameters: { city: 'Rome' }});
  // }

  // // Uncomment and edit to make your own Google Assistant intent handler
  // // uncomment `intentMap.set('your intent name here', googleAssistantHandler);`
  // // below to get this function to be run when a Dialogflow intent is matched
  // function googleAssistantHandler(agent) {
  //   let conv = agent.conv(); // Get Actions on Google library conv instance
  //   conv.ask('Hello from the Actions on Google client library!') // Use Actions on Google library
  //   agent.add(conv); // Add Actions on Google library responses to your agent's response
  // }
  // // See https://github.com/dialogflow/dialogflow-fulfillment-nodejs/tree/master/samples/actions-on-google
  // // for a complete Dialogflow fulfillment library Actions on Google client library v2 integration sample

  // Run the proper function handler based on the matched Dialogflow intent name
  let intentMap = new Map();
  intentMap.set('Default Welcome Intent', welcome);
  intentMap.set('Default Fallback Intent', fallback);
  // intentMap.set('your intent name here', yourFunctionHandler);
  // intentMap.set('your intent name here', googleAssistantHandler);
  agent.handleRequest(intentMap);
});

 コメントアウトしてある関数が2箇所あります。コメントアウトを解除してintentMapに紐づけている箇所のコメントも外します。 'your intent name here' のところはあらかじめ作っておいたインテントの名前を書きます。

 2つの関数のうち、yourFunctionHandler のほうは問題ないのですが、googleAssistantHandlerのほうを有効にして、シミュレーターで実行しようとするとエラーが出ます。

f:id:inosyan:20181123231450p:plain

Failed to parse Dialogflow response into AppResponse because of empty speech response. 

 これだけだと原因がわからないので、プログラムで何が起きてるのかログを見てみます。画面下のリンクからFirebaseのログが開きます。

f:id:inosyan:20181123231507p:plain

 ログにはこのようにありました。

f:id:inosyan:20181123231522p:plain

TypeError: Cannot read property 'forEach' of undefined
    at V2Agent.addActionsOnGoogle_ (/user_code/node_modules/dialogflow-fulfillment/src/v2-agent.js:313:28)
    at WebhookClient.addResponse_ (/user_code/node_modules/dialogflow-fulfillment/src/dialogflow-fulfillment.js:269:19)
    at WebhookClient.add (/user_code/node_modules/dialogflow-fulfillment/src/dialogflow-fulfillment.js:245:12)
    at googleAssistantHandler (/user_code/index.js:49:12)
    at WebhookClient.handleRequest (/user_code/node_modules/dialogflow-fulfillment/src/dialogflow-fulfillment.js:303:44)
    at exports.dialogflowFirebaseFulfillment.functions.https.onRequest (/user_code/index.js:60:9)
    at cloudFunction (/user_code/node_modules/firebase-functions/lib/providers/https.js:57:9)
    at /var/tmp/worker/worker.js:714:7
    at /var/tmp/worker/worker.js:697:11
    at _combinedTickCallback (internal/process/next_tick.js:73:7)

agentの内部で起きている問題のようです。解決するには actions-on-google のバージョンを変えます。

タブを index.js から package.json に切り替えます。

f:id:inosyan:20181123231544p:plain

package.json

{
  "name": "dialogflowFirebaseFulfillment",
  "description": "This is the default fulfillment for a Dialogflow agents using Cloud Functions for Firebase",
  "version": "0.0.1",
  "private": true,
  "license": "Apache Version 2.0",
  "author": "Google Inc.",
  "engines": {
    "node": "8"
  },
  "scripts": {
    "start": "firebase serve --only functions:dialogflowFirebaseFulfillment",
    "deploy": "firebase deploy --only functions:dialogflowFirebaseFulfillment"
  },
  "dependencies": {
    "actions-on-google": "^2.1.0",
    "firebase-admin": "^5.13.1",
    "firebase-functions": "^2.0.2",
    "dialogflow": "^0.6.0",
    "dialogflow-fulfillment": "^0.5.0"
  }
}

このうち、 dependenciesの

    "actions-on-google": "^2.1.0",

    "actions-on-google": "2.4.0",

に変更すると直ります。 サンプルとバージョンの不一致の問題なので、近い将来には解決すると思います。
 
 

デプロイまでに時間がかかるのでその対策

プログラムを修正してDeployボタンを押すとデプロイを開始します。
f:id:inosyan:20181123231602p:plain

そしてしばらくするとデプロイが終わったと表示されます。
f:id:inosyan:20181123231618p:plain

でもこの時にはまだシミュレーターで見ても修正が反映されていません。反映までにはここから1〜2分ほどかかるようです。反映されたかどうかを知るため、僕の場合は会話が開始された時のインテント「Default Welcome Intent」の最初のセリフにバージョンを喋らせています。

agent.add(`バージョン${VERSION}`);

 
 

日本語のプライバシーポリシーが必要

 作ったアプリをリリースするにはプライバシーポリシーを用意しなければなりません。ですがほとんどの人は作ったことはないと思います。Googleもそこはわかっているようで、テンプレートを用意してくれています。
f:id:inosyan:20181123231639p:plain

プライバシーポリシーの画面で Need help creating a Privacy Policy? を選ぶと、このようなダイアログが開きます。 f:id:inosyan:20181123231659p:plain

 ここの sample doc のリンクを開くと、テンプレートのドキュメントが開くのですが、テンプレートの説明は書いてあるのに肝心のテンプレートがありません。

テンプレート内容が書かれていないサンプルドキュメント

f:id:inosyan:20181123231720p:plain

 運良く僕は、テンプレートが書かれているバージョンのリンクを知っていました。少し前に作ったプロジェクトの同じダイアログにある sample doc のリンクには、別のドキュメントへのリンクが設定されていて、そちらのドキュメントにはテンプレートの文章が書かれていました。

テンプレート内容が書かれているサンプルドキュメント

f:id:inosyan:20181123231741p:plain

 このテンプレートの指示どおり、${APPNAME}と${DEVELOPER}の箇所を自分のアプリ名と開発者名に置き換えて、手順の箇所を削除して共有設定を誰でも見れるようにしたものをプライバシーポリシーとして申請しました。

 翌日、審査に落ちたとのメールが来ました。理由はプライバシーポリシーが日本語では無かったからです。僕が作ったアプリは日本語用だったので、その場合は日本語のプライバシーポリシーが必要とのことでした。日本語に訳して再提出したら審査に通りました。ご参考までにそのドキュメントのリンクを載せておきます。もしこれを元に作成する場合は、アプリ名「バトルゲーム」と、開発者名「イノシャン」は、ご自身の情報で置き換えてください。くれぐれもよろしくお願いします。

バトルゲーム プライバシーポリシー
 
 

アプリは「終了」で終わらせる必要がある

 はじめの審査で落ちた理由はもう一つあり、それは「終了」の言葉でアプリが終わらなかったことです。アプリが終了したときに連勝記録を喋らせたかったので、デフォルトのインテントは使わずFulfillmentに処理を書いて起き、「中断」という言葉でその処理を行うようにしていました。ですが、「終了」という言葉でも終わらせないといけないようです。なので、こんな感じで Entity の「中断」を「終了」に変え、念のため似たような言葉に反応するようにしました。

f:id:inosyan:20181123231756p:plain

ちなみに、conv.close を呼ぶと終了させることができるようです。

        const conv = agent.conv();
        conv.close(msg);
        agent.add(conv);

 
 

会話ごとに保持する変数の扱い

 作り方のサンプルを見ると、例えばホテルの予約システムの場合、システムが提示した選択肢の中からユーザーが選んだものを会話中システムが保持することは出来るようです。ですが、例えば「連勝記録」のような、ユーザが選択肢から選ぶようなものではなく、システムが増減させることのできる変数をどうやって作るのか、はじめは分かりませんでした。

 例えば 'win' という変数に連勝記録を保持するとします。その変数を function の外側に置いておけば、連勝記録は保持されます。一見うまくいったように見えますが、これがうまくいくのは同時にプレイしているユーザーが一人の時だけです。2台の端末から同時にアクセスするとどちらも同じ連勝回数になってしまい、混乱してしまいます。

 このことから、どうやらこのプログラムは、会話ごとに別のインスタンスが作られるわけではなく、1つのインスタンスがすべての会話を処理していることがわかります。なので変数はテーブルにして、会話ごとにデータを分けるようにしました。 request.body.session は会話のセッションごとに違う値が入っているので、キーとして使えそうです。

const userId = request.body.session;
statusTable[userId] = data;

 ただ、このままでは会話ごとにデータが増えるのでメモリが心配です。終了コマンドが呼ばれたらdeleteするようにはしていますが、途中で会話をやめるかもしれず、そうなるとデータが残り続けてしまいます。なので、会話ごとのデータの更新日も記録し、1日以上経った古いデータを消す処理を会話の開始時に行うようにしています。

function deleteOldData() {
    const date = new Date();
    date.setDate(date.getDate() - 1);
    Object.keys(statusTable).forEach(key => {
        if (statusTable[key].date.getTime() < date.getTime()) {
            delete statusTable[key];
        }
    });
}

 もし1日に何百万人もアクセスするようなコンテンツなら、全件ループするこのような方法は良くないですが、GoogleアシスタントやDialogflowはアクセス数の上限があるのでその心配はありません。DBを使う手もありますが、今回のゲームはシンプルに作りたかったので使いませんでした。もっとスマートなやり方があるのかもしれませんが、今回はこうしました。


 いくつか注意が必要な箇所はありましたが、それでもリリースまでわりと簡単に出来て面白いと思いました。審査も1回落ちましたが申請して翌日には結果が返ってくるので合計2日しかかかりませんでした。開発は絵を書くこと以外はすべてWebブラウザ上で出来ました。コードもInline Editorで書きましたが、行数が多くなるとブラウザでは狭いので、ローカルで書いたものをデプロイするほうが良いかもしれません。ですが、簡単な処理だけしか書かないのならInline Editorで充分です。気軽にできるので趣味でプログラムを書く人にもおすすめです。

指輪型ガジェット「ORII」のBackerになった

f:id:inosyan:20181106075456j:plain

 僕はガジェット好きなので、たまにKickstarterのようなクラウドファンディングを利用するのですが、久々にグッとくる指輪型のガジェット「ORII(オリー)」を見つけました。

【プロジェクトサイト】指がスマホになる!多機能をスマートにこなす、ウェアラブル革命「ORII」

 このガジェットの最大の特徴は「指を耳に当てて音を聞ける」です。骨伝導なので音は周りに漏れず自分だけに聞こえます。指がイヤホンがわりになるので、イヤホンを持ち歩いたり装着する手間がありません。

f:id:inosyan:20181106075509j:plain

 指一本で電話がかけられる点が、おそらく多くの人の目を引くセールスポイントだと思いますが、僕は普段あまり電話をしないので関係ありません。それより期待しているのは音声アシスタントとの連携です。音声アシスタントは面白いのでたまに使いますが、普段スマホのスピーカーはオフにしてあるので、音量をあげるかイヤホンを繋げなければなりません。周りに人がいると音量を上げれないし、イヤホンを挿すのも面倒なので、結局音声無しでスクリーン表示だけで利用します。しかしそれだと面白さが半減してしまいます。やはり人としゃべっている感覚で、音声で返事が返ってくるほうが断然面白いです。

 これがORIIなら、でいつでもどこでも音声アシスタントを使えます。指を耳に当てるだけなので気軽です。

 ORIIでやりたいことを挙げてみました。

  1. ニュース記事の中の動画などの音声をイヤホン無しでも聞きたい
  2. 手元にスマホやパソコンがない時でもネットで調べ物がしたい
  3. 自分で音声アシスタントのアプリを作りORIIから呼び出したい

 特に3番目をやってみたいです。まだあまり音声アシスタントについて知らないのでこれから調べますが、場所やシチュエーションに関係なくいつでもこれが利用できれば、やれることは無限です。事前に音声アシスタントについて調べてみたくなりました。

 ORIIの情報を見ると、音声アシスタントとして「Siri」と「Googleアシスタント」の2つが挙げられていました。Alexaについては書かれていませんでしたが、もしかしたら動くかもしれません。しかし僕のスマホAndroidなので、素直にGoogleアシスタントをターゲットとして考えてみます。

 開発の仕方のちょっと調べればすぐに見つかる良い時代です。

DialogflowでGoogleアシスタント対応アプリを作成しよう

 しかし、気になることもあります。それは……

周りに人の居る静かな場所では音声コマンドを使いづらい

 人がいても居酒屋のようなガヤガヤしたところだったら平気ですが、電車の中ではちょっと難しそうです。

 解決策がないか調べたところ、面白い記事を見つけました。

これで恥ずかしくない!?声を出さずに「音声入力」できるシステムをMicrosoftの研究者が開発

 まだ研究段階ですが、この「SilentVoice」がデモンストレーション動画の中に出て来た指輪型のデバイスに応用できれば、人目を気にせずに音声コマンドを使えそうです。

 発送は12月末予定とのこと。プロジェクトの成功を期待します。

【マイクラMakeCode】カスタムブロックの作り方 その3 ~フォルダの見た目~

f:id:inosyan:20181029233850g:plain
 
 
 前回は、カスタムブロック1のコードのうち、namespace(名前空間2がカスタムブロックのフォルダの名前になることについて説明しました。今回はフォルダの見た目を変える方法ついて説明します。  
 

名前空間の上に書いてあるコメント

 前回作ったカスタムブロックのファイル custom.ts を開きましょう。

/**
 * Custom blocks
 */
//% weight=100 color=#0fbc11 icon=""
namespace inosyan_testapp {
    ...
}

   
 名前空間の上に、/***/で囲まれている部分 や // で始まる箇所がありますね。これらはプログラムとしては認識されない「コメント」と呼ばれる部分で、実際のプログラムの動きには影響を与えません。コメントはそのコードがどんなものなのか、他の人や自分があとで見てわかるように説明を書いておくためのものです。

 /***/で囲まれたコメントには、そのすぐ下にあるコードの説明を書きます。この例では 名前空間「inosyan_testapp」の説明として「Custom blocks」と書いてあるのですが、inosyan_testapp の部分にマウスオーバーすると、その文章がポップアップされます。これは、他のコードから参照するときに説明が見れるので便利ですが、別に書かなくても構いません。

f:id:inosyan:20181029233915g:plain
 
 カスタムブロックを作る上で大事なのは、その下の //% で始まるコメントです。// だけなら普通の1行コメントですが、その後ろに % がついているところに注意してください。//%で始まるコメントが名前空間の上に書いてあると、その名前空間がカスタムブロックのフォルダであることを表します。

 名前空間の上に書いてるこの部分に注目してください。

f:id:inosyan:20181029233928g:plain
 
//%の後ろにスペースを開けて - weight=100 - color=#0fbc11 - icon=""

の3つが書かれていますね。これらのそれぞれの役割について説明します。
 
 

フォルダの順番

 フォルダはこのように並んでいますね。「INOSYAN_TESTAPP」が一番上に来ています。

f:id:inosyan:20181029233943g:plain
 
 表示の順番は weight できまります。数字が高ければ高いほど上に表示されます。エクスプローラーで core の中の ns.ts というファイルを見てみてください。そこに、そのほかのフォルダのweightが書かれています。

f:id:inosyan:20181029233954g:plain

 イノシャンの作品の場合は、下のほうに控えめに表示されるように weight=1 にしてあります。  
 

フォルダの色

 フォルダの色は、 color=#0fbc11 の #0fbc11 の部分を変えることで別の色にすることができます。これは「カラーコード」と言って、「 #(シャープ)」とそれに続く16進数3で表します。

 色を表現する方法の一つに、赤、緑、青の3色の組み合わせて表現する方法がありますが、このカラーコードは、赤、緑、青をそれぞれ 0 ~ 255 の強さで表し、2桁の16進数にしたものを3つつなげて書いたのです。

 フォルダの色の#0fbc11 は、赤が 0f (10進数で15)、緑が bc (10進数で188)、青が 11 (10進法で17)で、緑の成分が多いので緑色をしています。

 例えば、これを赤にしたい場合は、赤の成分を多くすればいいので、赤を ff(10進数で255)、緑を 00 (10進数で0)、青を 00 (10進数で0)にしたカラーコード #ff0000 にします。

//% weight=100 color=#ff0000 icon=""

f:id:inosyan:20181029234010g:plain
 
 
 色を表すもう一つの方法に、色名で表す方法があります。例えば、赤色は red、青色は blue です。試しに紫色 (purple)にしてみましょう。

//% weight=100 color=purple icon=""

f:id:inosyan:20181029234027g:plain

 他にもいろんな色があります。色名をまとめてあるサイトがありますので、こちらのサイト をご覧ください。  
 

フォルダのアイコン

 カスタムブロックのフォルダに使われているアイコンは、実は文字と同じ方法で表示されています。

f:id:inosyan:20181029234044g:plain

 このアイコンは icon="" の値を変えることで、他のアイコンに変えることができます。ですが、エディタ上のアイコンの値はただの四角形にしか見えません。表示されるアイコンは、三角フラスコの形をしているのに不思議ですね。

f:id:inosyan:20181029234100g:plain

 この四角形にしか見えないところには、Unicodeユニコード4がはいります。なぜ四角にしか見えないかと言うと、エディタが使用しているフォント5がそのアイコンのコードに対応してないからです。Unicodeは世界中の文字(図形を含む)を扱うためのものですが、フォントデータはUnicodeの文字すべてに対応しているわけではありません。表示できない文字コードは四角で表示されます。

 一方、カスタムブロックのフォルダを表示するのに使われているフォントは、アイコンのUnicodeも表示できるものが使われているので、ちゃんとアイコンとして表示されます。

 アイコンの値は、他の方法を使って指定することができます。 icon=""
のところを icon="\uf118"
に置き換えてみましょう。

 いったんHomeに戻って再びプロジェクトを開くと、アイコンがフラスコからスマイルに変わっていますね。

f:id:inosyan:20181029234118g:plain

 f118のように、Unicodeの文字にはアルファベットと数字を組み合わせた記号が割り当てられています。 例えば、Font Awesome のページで、'smile' を検索すると、スマイルのアイコンが出てきますね。

Font Awesomeでのsmileの検索結果

f:id:inosyan:20181029234135g:plain

 詳細ページをみると、Unicodeの記号f118がわかるので、カスタムブロックに使うアイコンには、それを指定しましょう。

f:id:inosyan:20181029234151g:plain

 プログラムで指定する際には、ほかの文字と区別するために記号の前に\nをつけます。

 アイコンは他にもたくさんあります。かっこいいアイコンを見つけたら、そのUnicodeの記号を設定してフォルダのアイコンとして表示されるか見て見ましょう。表示できない場合もありますが、多くのアイコンに対応しているようです。

 せっかく作るカスタムブロックですので、かっこいいアイコンを設定しましょう!


  1. カスタムブロックとは、ユーザーが定義したプログラムブロックのこと。JavaScript(TypeScript)で書くことができ、他のプログラムブロックと同じようにならべて使うことも、JavaScriptから呼び出すこともできる。

  2. namespace(名前空間)とは、変数名や関数名などが重複しないようにするための仕組み。関数名などの名前はコンピューターがそれを特定できるようにするため、それぞれ違った名前にしなければならないが、他の人が作ったライブラリを使ったりプログラムが大きくなると、偶然同じ名前になってしまうことがある。namespaceでプログラムを囲むことにより、他のプログラムとの重複を避けることができる。

  3. 16進数とは、数字の表し方の一つで、16で1つ桁が繰り上がる。0 ~ 9までは10進数と同じだが、10をA,11をB,12をC,13をD,14をE,15をFと表し、16は桁があがって 10 と表す。10進数と区別するために 0x を頭につけて 0x10 のように表すことが多い。

  4. Unicodeユニコード)とは、文字コードの一つ。コンピューターでは文字を表現するのに文字コードと呼ばれる符号(記号)を用いるが、Unicodeは世界中の文字を扱えることを目指す。

  5. フォントとは、文字の形状のデザインのことだが、コンピュータにおいてのフォントは、文字コードを画面に表示するのに使う、文字の形状を集めたデータファイルのことである。