NPCのAIプログラミング

こんにちは。開発担当のマットです。
 
ゲームが大好きです。
パソコンゲームと最初に出会ったのは子供の頃でした。
父親の事務所にあったパソコンにMS-DOSゲームライブラリーというものがあって、楽しく遊んでいたことを覚えています。
 
ライブラリーには色々なゲームがありましたが、その中でもシミュレーションゲームが特に好きでシムシティのようなゲームに何時間も費やしていました。

代表的な街づくりゲーム。シムシティ2000

遊んできたたくさんのゲームの中で、大きな印象があったのが2001年発のBlack&Whiteです。
ちょっと珍しい設定ですが、このゲームは村の神様として遊びます。
雨を降らしたり、火災を起こしたり、雷を落としたり、様々な神秘的な奇跡を起こすことによって崇拝してもらうゲームです。

Black&White の一般的な村

でも、一番面白いと思っていたのはゲームの中の村人達でした。
ゲーム用語で、このようなキャラクターは「直接制御できないキャラクターNPC(ノンプレイヤーキャラクター)」といいます。
このゲームの一番の魅力はこのNPC達のAIでした。

なぜなら他のゲームと違って、ある程度自由に動いたりすることができたからです。
村の婦人達が妊娠したり、子供達が遊んで走り回ったり、皆が怖いことから隠れたりして、見るだけでも十分面白いシミュレーションでした。

AIとは何?

AIはArtificial Intelligenceの省略です。
日本語では、「人工知能」と訳されています。

ゲームのAIは「知能」の意味が曖昧で中々定義しにくいですが、環境に反応して目的を果たそうとするものはAIと言っていいかと思います。

上記で取り上げたBlack&Whiteの例では、村人達の目的は単純に無事に幸せに生きて子供を残すことだけでしたが、そのことでゲームに個性が生まれて楽しく遊ぶことができました。

この記事で紹介するAIの例

この記事では、上記のようなAIプログラムの基本を説明したいと思います。
実際のコードは使いませんが、AIの基盤がわかるとプログラミング言語を1つでも知っていれば、誰でもAIスクリプトを書くことに挑戦できると思います。

上記のゲームと同様で自由勝手に動く人間(NPC)のAIの例で進めたいと思います。
何らかの設定が必要なので、「無人島に流された」という設定を使いたいと思います。

ゲームAIの分解

AIのプログラムは5つの段階に分けることができます。

  1. Sensing – 感知する
  2. Conditions – 条件を生成する
  3. State – 状態を選択する
  4. Schedule – (適切であれば)新たなスケジュールを選択する
  5. Task – タスクを実行する

定期的にスクリプトをコールして、これらの段階を順番に実行していきます。

1. Sensing – 感知する

まず一番最初にするのは「感知」です。
NPCはただの人間で全知の超能力者ではありません。
そのNPCは何を見えるか、何を聞こえるかをリストアップし、もしNPCの近くにココナッツの木やビーチで歩いているカニがいたら、それらをNPCの感知リストに載せる。

逆に島の反対側にいるイノシシは見えないし聞こえないので、感知リストには載せません。

2. Conditions – 条件を生成する

次は感知したものリスト一覧で様々な「条件」を生成する。
例えばカニが見えるので「食物が見えるか」の条件をtrueと定義する。
同じくココナッツが見えるので「水分が見えるか」の条件もtrueと定義できる。
その反面イノシシのような獣は感知していないので「危険な動物がいる」の条件をfalseと定義する。

3. State – 状態を選択する

次はNPCの状態を選択する。
まずは「通常」ですが、もし近くにいる危険な動物を感知したら「危険な動物がいる」という条件がtrueになります。
もちろん危険動物が近くにいると振る舞いが変わるので、NPCの状態(State)が「通常」ではなくて「注意深い」になります。

「注意深い」になっている間は、音を立てずに忍足で歩くなど行動が変わります。

一つの状態の種類だけだと足りないかもしれないので、さらに分けることができます。
例えば「心理の状態」と「健康の状態」など。

4. Schedule – スケジュールを選択する

スケジュールとは一つの目標のことです。
上記で定義した条件と状態の元で決めます。

例えば水分が近くにあって喉が乾いている場合、水分を取りに行くというスケジュールを決定する。

基本的にスケジュールが決定されたら、そのスケジュールを最後までする。*1
つまり水分を取りに行っている途中、お腹が空いてもスケジュールは変更しません。
まず水分を獲得してから食料を取りに行けばいい。*2

しかし「割り込み条件」(Interruption)という特別な条件が成立したら、途中であってもスケジュールを変更する。
例えば蛇に噛まれた場合は水分獲得をやめてケガを治療するスケジュールに切り替えるべき。

*1: これは柔軟なルールです。途中で再検証すべきスケジュールもあると思います。
*2: ただし「餓死しそう」のような「割り込み条件」が成立したらスケジュールを変更すべきです。

5.Task -タスクを実行する

スケジュールは一つか複数のタスクで成り立っています。

例えばココナッツの水分を獲得するのに、以下のタスクがある。

  • 木まで歩く
  • 木を登る
  • ココナッツを取る
  • 木を降りる
  • ココナッツを割る
  • 飲む

「割り込み条件」によってスケジュールが変わらない限り、これらのタスクを1つ1つ順番にやり遂げるだけです。

まとめ

ゲームを開発する際、気の利いたAIが必要で欠かせない場合がたくさんあります。

プログラマーとして「あ!動いた!」の瞬間が一番楽しいと思うので、実際に動き回るバーチャルな人間のようなNPCをコーディングすることはとても楽しいです。
興味がある方は実際にプログラムでも書いてみるのはいかがでしょうか。


おたぐちさんのLINEスタンプをリリースしました

こんにちわ。
リエです。
 
6月21日(水)に弊社よりクリエイター田口美早紀さん(以下おたぐちさん)のLINEスタンプ「日常の猫たち」をリリースしました!

 
おたぐちさんといえば、昨年弊社よりLINEスタンプ「スポーツたち 球技編」をリリースさせていただきましたが、今回はおたぐちさんシリーズの第2弾となります。
前回のおたぐちさんのLINEスタンプの記事はこちら
 
おたぐちさんが描く猫ちゃんが日常のあれこれをLINEスタンプで表現しています。
第2弾のスタンプは言葉が入っているものがいくつかあり、「おはようございます」「こんにちわ」など日常使いできるものから「しばらくお待ちください」などあると便利!なものまで、シチュエーションに応じて使っていただける楽しいスタンプとなっております。
 
またこの猫ちゃんの表情がたまらないんですよね〜♡
猫ちゃん好きな方にも満足いただけるスタンプだと思います(=^・^=)
 
ぜひぜひ日常のアレコレをこのスタンプを使って表現しちゃってください♪


エムグラム性格診断で見えた自分とは?

こんにちわ。
リエです。
 
最近ローソンのアイスカフェラテにハマっています。
量もたっぷりで美味しいのです♡
ついつい買っちゃいます。
 
少し前にインスタで話題になった、m-gram診断というものを知っていますか?
ーーーーーー
〈m-gram診断とは?〉
性格成分44種類のうち、あなたの性格で強く出ている8成分を精密に抽出します。
上場企業を含む9,000社以上、延べ1,000万人以上の性格データに基づき分析するので、極めて精密に分析できます。
ーーーーーー
引用元:m-gram診断診断
 
自分の性格を8成分で表すといったものなんですが、わたしも試してみました。
結果はこちら。

 
当たっているのは#合理主義者と#凝り性ですかね。(自他共に求める頑固者です)
#石橋を叩きまくって渡ると#保守的は違うような気がします。
割と行動派なので(๑•̀ㅂ•́)و✧
でも時と場合によりますよねぇ。実際はどうなんでしょ。
 
ちょっと自分を丸裸にされたようで恥ずかしかったですが^^;
あまり自分の性格分析をすることはないので、おもしろかったです。
興味のある方は試してみてはいかがでしょうか。
 


スマホでもPCでも読みやすい!HTMLメールのフォントサイズ

HTMLメールをスマホでみて文字が小さい…
PCとスマホ両方対応しようとしたら、文字が小さくなった…
などといった経験をしたことがないでしょうか?

 

PCで見ると普通なのに、幅を600px等に指定してスマホで見ると文字が小さくなります。
iPhoneのデフォルトのメーラーだと以下の様になります。
(幅を指定していなくても大きめの画像を表示しただけでも、文字が小さくなるようです。)

単純にフォントサイズを大きくすればいいかもしれませんが、そうすると今度はPCで文字が大きくなりすぎてしまいます。

 

その回避方法としてtableタグに「width=”600″」や「style=”width:600px;”」を使わずに、「style=”max-width:600px;” align=”center”」を指定してみましょう。
そうすると、スマホでは、文字の大きさは縮小されずに表示され、PCでは、幅600pxで画面の真ん中に表示されるようになります。

 

しかし、、、
outlookでは、max-widthに対応していないため、幅いっぱいに表示されてしまいます。


ビロ~ン (ノД`lll)

 

outlook2016などを無視していい案件であればこのままでもいいのですが、そういうわけにいかない場合、条件付きコメントを使って、そこにテーブルタグで囲う事で幅を固定できます。

<!-- [if (gte mso 9)|(IE)]>
outlook用のコードを記入
<![endif]-->

「gte mso 9」は「Outlook 2000 – Version 9以上」という事のようです。
【参考】Outlook Conditional CSS

 

具体的に次のように書くとoutlookでは、幅600pxで表示されます。

<!--[if (gte mso 9)|(IE)]>
<table width="600" align="center" style="width:600px;"><tr><td>
<![endif]-->

<table align="center" border="0" cellpadding="0" cellspacing="0" style="max-width:600px;background-color:#ffffff;" >
  <tbody>
    <tr>
      <td>
        <h1>タイトル(h1タグ)</h1>
        <p>紹介文テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト(pタグ)</p>
        <img class="content-banner-img" src="https://placehold.jp/600x300.png" alt="" />
      </td>
    </tr>
  </tbody>
</table>

<!--[if (gte mso 9)|(IE)]>
</td></tr></table>
<![endif]-->


フォントサイズで困っている方は一度試してみて下さい!

 

表示確認ができるサービス

HTMLメールを表示確認する有名なサービスとして、下記の2つがあります。
使ってみると、想定外な所が崩れている…ということがあるので、使ったことが無い方は1度試してみるのもいいかもしれません。

 


プリンタのインク残量が低下したらSlackに通知する方法

IoT(Internet of Things, モノのインターネット)という言葉が存在感を増し続けている今日この頃ですが、ネットワーク対応のプリンタはIoT機器の元祖とも呼べる存在です。
そのネットワーク機能を使ってちょっとした業務改善をしてみました。

課題

プリンタを使おうとしたときに、インク切れですぐに印刷を始められないという状況を無くしたい。

印刷しようとしたときにインクが切れてるとイラッとしますよね。

対策

プリンタのネットワーク機能を使ってインクの残量を監視して、一定量よりも少なくなったらSlackで通知します。
監視用のサーバとして、当社の勤怠システムでも使っているRaspberry Piを利用します。

調査

オフィスで使っているプリンタはCanonのMG7530です。
問題はどうやってインクの残量を取得するかというところです。
初めはMG7530のLinux用ドライバをRaspberry Piにインストールしてそこからなんとか取得できないかと考えたのですが、どうやらこのドライバではインクの残量はサポートしていないようです。

そこで、MG7530のウェブインタフェースから取得することを考えました。
プリンタのIPアドレスにHTTPアクセスすると以下のような管理用ウェブUIが利用できます。

しかしながら、該当ウェブページのソースを見てもインク残量のデータそのものは含まれておらず、単純なスクレイピングはできそうもありません。

                <div class="InkPattern" id="inktank0">
                    <div class="InkMess"></div>
                    <div class="InkArea"><div class="InkBar"></div></div>
                </div>
                <div class="InkPattern" id="inktank1">
                    <div class="InkMess"></div>
                    <div class="InkArea"><div class="InkBar"></div></div>
                </div>
                <div class="InkPattern" id="inktank2">
                    <div class="InkMess"></div>
                    <div class="InkArea"><div class="InkBar"></div></div>
                </div>
                <div class="InkPattern" id="inktank3">
                    <div class="InkMess"></div>
                    <div class="InkArea"><div class="InkBar"></div></div>
                </div>
                <div class="InkPattern" id="inktank4">
                    <div class="InkMess"></div>
                    <div class="InkArea"><div class="InkBar"></div></div>
                </div>
                <div class="InkPattern" id="inktank5">
                    <div class="InkMess"></div>
                    <div class="InkArea"><div class="InkBar"></div></div>
                </div>
                <div class="InkPattern" id="inktank6">
                    <div class="InkMess"></div>
                    <div class="InkArea"><div class="InkBar"></div></div>
                </div>

これはどうやらJavaScriptで値を取得して埋めてるパターンだろうということでブラウザの開発ツールを開いてしばらく見張っていると、prninfo_data.cgiというCGIに定期的なアクセスが発生していることが分かりました。

prninfo_data.cgiリクエストへの応答はこんな感じ。

<?xml version="1.0" ?>
<response>
<PAGE_PRNINFO>
<INKREST0>0,0,0</INKREST0>
<INKREST1>5,6,0</INKREST1>
<INKREST2>4,2,0</INKREST2>
<INKREST3>1,2,0</INKREST3>
<INKREST4>2,7,0</INKREST4>
<INKREST5>3,3,0</INKREST5>
</PAGE_PRNINFO>
(略)

1桁目は全て異なる数値になっているのでインクの種類だろうということは想像できます。
2桁目はインク残量っぽいけど数値の意味がはっきりしません。3桁目はなんだろう?

データの意味を調べるためにprninfo_data.cgiを呼び出しているJavaScriptのファイルを調べたところ、こんな関数を発見しました。

function dispInkInfo(inktank_info, idStr) {

    var inkLVL = ['Lv100', 'Lv090', 'Lv080', 'Lv070', 'Lv060', 'Lv050', 'Lv040', 'Lv030', 'Lv020', 'Lv010', 'Lv000', 'Lv00X'];
    var inkMRK = ['InkMess', 'InkMess LwInk', 'InkMess NoInk', 'InkMess UnInk'];

    var inkCOL = ['InkBlk', 'InkPbk', 'InkCia', 'InkMaz', 'InkYel', 'InkGry', 'InkClr', 'InkBlk', 'InkCMYK'];

この関数の後ろの方を読むと、たとえば、<INKREST1>5,6,0</INKREST1>というデータはinkCOL[5](‘inkGry’), inkLVL[6](‘Lv030’), inkMRK[0](‘InkMess’)という風に使われるようです。
インク残量の画像と比べてみると、グレイが30%くらいになっているのでこれで合ってそうですね。

ということは、CGIの応答の3桁目が1(‘InkMess LwInk’)になっていたら通知すれば良さそうです。

実装

というわけで監視用のスクリプトを実装しました。

#!/bin/bash -eu

PRINTER_IP=X.X.X.X

function post_message() {
    check_dir=/home/pi/printer_ink_monitor
    check_file=POSTED

    exists=`find $check_dir -name $check_file -mtime -1|wc -l`
    if [ $exists -ne 0 ]; then
	return
    fi
    touch $check_dir/$check_file
    curl -X POST --data-urlencode 'payload={"channel": "#channel1", "username": "MG7530", "text": "プリンタのインク残量が僅かになりました", "icon_emoji": ":printer:"}' https://hooks.slack.com/services/XXX/YYY/ZZZ
}


xml=`curl -m 2 -s "http://${PRINTER_IP}/rui/prninfo_data.cgi" -H 'Content-Type: application/x-www-form-urlencoded' -H 'Authorization: Basic HOGEHOGE' --data 'GETINFO=0'`

result=`echo $xml | /home/pi/printer_ink_monitor/parsrx.sh |grep INKREST |cut -d, -f3`
for status in $result; do
    # 0: normal, 1: Low Ink, 2: No Ink, 3: Un Ink(?)
    if [ $status -eq 1 ]; then
	post_message
    fi
done

parsrx.shでXMLのデータを行指向のデータに変換しているので、以下のようにしてインク残量のデータだけを取り出せます。
この数値がどれか1個でも1になっていたら通知するようにしています。

$ curl -m 2 -s "http://X.X.X.X/rui/prninfo_data.cgi" -H 'Content-Type: application/x-www-form-urlencoded' -H 'Authorization: Basic HOGEHOGE' --data 'GETINFO=0' | bash printer_ink_monitor/parse_xml.sh | grep INKREST | cut -d',' -f3
0
0
0
0
0
0

また、通知が連続するとうっとうしいことになるので、1日1回だけ送るようにしています。

テスト

インク切れの状態はなかなか作り出せないので、上のスクリプトをいじってインク残量が0だったら通知するようにしてテスト実行してみました。

通知来た!

まとめ

完成したので毎分チェックするようにcronを設定しました。(毎分なのは、プリンタは使う時だけ電源を入れるルールなので、電源が入っているタイミングを逃さないためです)

先日インク切れが発生し通知がきたので、無事印刷をする前にインク交換をすることができました。