あかんわ

ブログで日誌でも書くようにすれば多少はやる気が出るかと思ったんです

恋するプログラムをSinatraでWebアプリにするPart.6[CHAPTER6 感情コントロールの魔術師]①

記事の概要

『恋するプログラム』の[CHAPTER6 感情コントロールの魔術師]を参考に、感情モデルを搭載したチャットボットを作成します。

目次

開発環境

参考記事
Rubyの開発環境構築は、こちらの記事を参考にさせていただきました。

ノビィの機嫌を取る

チャットボットに感情モデルを導入し、特定の単語を含む会話で応答時の口調を変化させます。
また、応答時に喋るアニメーションと、通常時に周囲を見回すアニメーションも追加します。

アプリケーションディレクトリの構成
~/programinlove
    |- proto.rb                 // CUIチャットボットのメインファイル
    |- unmo.rb                  // チャットボットオブジェクトのモデル
    |- responder.rb             // 応答オブジェクトのモデル
    |- dictionary.rb            // 辞書を読み込み管理するクラス
    |- /dics                    // 辞書ファイルを格納するディレクトリ
    |     |- random.txt         // ランダムな応答を返すための辞書
    |     |- pattern.txt        // パターンに合った応答を返すための辞書
    |- noby.rb                  // Webアプリのメインファイル
    |- /views                   // ビューのテンプレートを配置するディレクトリ
    |     |- index.erb          // チャットのインターフェースを表示するビュー
    |- /public                  // 静的ファイルを配置するディレクトリ
          |- nobycanvas.js      // キャラクターの画像をアニメーションさせるJavascript
          |- styles.css         // チャットのインターフェースをレイアウトするcss
          |- /img               // 画像ファイルを格納するディレクトリ
               |- /normal       // 平常時の画像を格納するディレクトリ
               |    |-0000.png
               |- /blink        // 瞬きの画像を格納するディレクトリ
               |    |-0000.png
               |    |-0001.png
               |- /lookaround   // 周囲を見回す画像を格納するディレクトリ
               |    |-0000.png
               |    |-0001.png
               |    |-0002.png
               |    |-0003.png
               |    |-0004.png
               |- /talk         // 喋る画像を格納するディレクトリ
                    |-0000.png
                    |-0001.png
ソースコード

キャラクターのアニメーションを増やしたらJavascriptの記述が増えてきたので、Javascriptindex.erbから分離し、nobycanvas.jsを作成しました。

nobycanvas.jsは、会話が入力されなければ、瞬きや周囲を見回すアニメーションを適当に表示していますが、会話が入力されると、noby.rbからdata属性経由で応答時であるという情報を取得し、喋るアニメーションを表示します。

その他のRubyファイルは、サンプルプログラムと同様の記述で、特に変更はありません。

views/index.erb
<html>
  <!-- publicフォルダのcssファイルを読み込む -->
  <link href="styles.css" rel="stylesheet">
  <body>
    <!-- チャットのインターフェースを囲う枠 -->
    <div id="nobyform">
      <!-- キャラクターの画像を表示 -->
      <div id="nobycanvas" data-nobystate="<%= @noby_state %>">
        <img src="img/normal/0000.png">
      </div>
      <!-- キャラクターからの応答メッセージを表示 -->
      <div id="responsearea">
        <p><%= @responder_resp %></p>
      </div>
      <!-- 会話やオプションの状態をPOSTするフォーム -->
      <div>
        <form action="/" method="post">
          <!-- ボタンを押すと会話を'/'にPOSTする -->
          <div>
            <input type="text" name="inputarea" placeholder="会話を入力">
            <button type="submit">話す</button>
          </div>
          <!-- ログにResponderを表示するオプション -->
          <div>
            <label><input type="checkbox" name="respoption" value="show" <%= @check %>>Responderを表示</label>
          </div>
        </form>
      </div>
    </div>
    <!-- 会話のログを表示 -->
    <div id="logarea">
      <%= @talk_log %>
    </div>
    <!-- キャラクターの画像をアニメーションさせるJavaScript -->
    <script src="nobycanvas.js"></script>
  </body>
</html>
public/nobycanvas.js
document.addEventListener('DOMContentLoaded', function(){
  /* 配列の要素番号 */
  var index = 0;
  /* キャラクターの情緒状態を確認 */
  var talking = document.getElementById("nobycanvas").dataset.nobystate;

  /* 通常時の画像 */
  var normalImg = ["img/normal/0000.png"];
  /* 瞬きのアニメーション用の画像 */
  var blinkImg = [
    "img/normal/0000.png",
    "img/blink/0000.png",
    "img/blink/0001.png",
    "img/normal/0000.png"
  ];
  /* 見回すアニメーション用の画像 */
  var lookImg = [
    "img/normal/0000.png",
    "img/lookaround/0000.png",
    "img/lookaround/0001.png",
    "img/lookaround/0002.png",
    "img/lookaround/0003.png",
    "img/lookaround/0004.png",
    "img/normal/0000.png"
  ];
  /* 喋るアニメーション用の画像 */
  var talkImg = [
    "img/normal/0000.png",
    "img/talk/0000.png",
    "img/talk/0001.png",
    "img/talk/0000.png",
    "img/talk/0001.png",
    "img/normal/0000.png"
  ];

  /* 関数の中で画像を操作するための配列 */
  var animePtn = [normalImg, blinkImg, lookImg];
  var imgAry = [];

  /* 3種類のアニメーションからランダムに選択 */
  function selectAnime() {
    imgAry = animePtn[Math.floor(Math.random() * 3)];
  }

  /* 画像を順番に表示してアニメーションを作成 */
  function flipAnime(){
    timeoutId = setTimeout(flipAnime, 100);

    document.getElementById("nobycanvas").getElementsByTagName("img")[0].src = imgAry[index];
    index++;
    if (index >= imgAry.length){
      index = 0;
      clearTimeout(timeoutId);
    }
  }

  /* 応答時は喋るアニメーションを表示 */
  if (talking) {
    imgAry = talkImg;
    talking = '';
    flipAnime();
  }

  /* 待機時は適当なアニメーションを選んで表示する */
  setInterval(selectAnime, 5000);
  setInterval(flipAnime, 5000);
});
noby.rb
require 'sinatra'
require 'sinatra/reloader'

require_relative 'unmo'


# 会話ログを格納する配列
log_area = []

# ヘルパーメソッドを定義する
# ルーティングメソッドの中で使う
helpers do
  def noby
    # ノビィ生成
    @noby ||= Unmo.new('noby')
  end

  def prompt(resp_opt)
    # 応答を表示する際のプロンプトを作成
    resp_opt ? "#{noby.name}#{noby.responder_name}" : "#{noby.name}"
  end
end

# URL'/'にアクセス
get '/' do
  # 会話ログを初期化してnobyfomを表示
  log_area = []

  erb :index
end

# URL'/'にPOSTメソッドでアクセス
post '/' do
  # ユーザの入力を取得
  talk_text = params['inputarea']

  # Responderを表示するチェックボックスの状態を取得
  # チェックされてる場合は状態を維持
  resp_opt = params['respoption']
  @check = "checked" if resp_opt

  # ユーザの入力があれば応答して会話ログに表示
  unless talk_text.empty?
    @noby_state = "talking"
    @responder_resp = noby.dialogue(talk_text)
    log_area << "> #{talk_text}<br>"
    log_area << "#{prompt(resp_opt)}> #{@responder_resp}<br>"
  end

  @talk_log = log_area.join

  erb :index
end

このコードのコミットには、[chapter6-5]のタグが付いてます。

実行結果

ターミナルでnoby.rbを動かし、ブラウザにhttp://localhost:4567を入力します。

~/programinlove
$ruby noby.rb

f:id:b0npu:20160107142020p:plain

運が良ければ、会話している気分になれます。

f:id:b0npu:20160131230312p:plain

参考書籍

参考記事

Sinatraについては、こちらの記事を参考にさせていただきました。

関連記事

- 恋するプログラムをSinatraでWebアプリにするPart.0[はじめに]
- 恋するプログラムをSinatraでWebアプリにするPart.1[CHAPTER3 ほんとに無能]
- 恋するプログラムをSinatraでWebアプリにするPart.2[CHAPTER4 あこがれのGUI]①
- 恋するプログラムをSinatraでWebアプリにするPart.3[CHAPTER4 あこがれのGUI]②
- 恋するプログラムをSinatraでWebアプリにするPart.4[CHAPTER4 あこがれのGUI]③
- 恋するプログラムをSinatraでWebアプリにするPart.5[CHAPTER5 辞書を片手に]
- 恋するプログラムをSinatraでWebアプリにするPart.7[CHAPTER6 感情コントロールの魔術師]②
- 恋するプログラムをSinatraでWebアプリにするPart.8[CHAPTER7 学習のススメ]①
- 恋するプログラムをSinatraでWebアプリにするPart.9[CHAPTER7 学習のススメ]②
- 恋するプログラムをSinatraでWebアプリにするPart.10[CHAPTER7 学習のススメ]③
- 恋するプログラムをSinatraでWebアプリにするPart.11[CHAPTER8 文章を作り出す]
- 恋するプログラムをSinatraでWebアプリにするPart.12[CHAPTER9 ノビィ、ネットワークにつながる]
- 恋するプログラムをSinatraでWebアプリにするPart.13[おわりに]