恋するプログラムをSinatraでWebアプリにするPart.6[CHAPTER6 感情コントロールの魔術師]①
記事の概要
『恋するプログラム』の[CHAPTER6 感情コントロールの魔術師]を参考に、感情モデルを搭載したチャットボットを作成します。
目次
開発環境
- OSX 10.11.2 El Capitan
- テキストエディタ: MacVim
- ターミナルエミュレータ: Macターミナル
- シェル: zsh
- パッケージマネージャ: Homebrew
- ブラウザ: Firefox - Ruby 2.0.0p645
- バージョンマネージャ: rbenv
- Webフレームワーク: Sinatra
参考記事
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の記述が増えてきたので、Javascriptをindex.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
運が良ければ、会話している気分になれます。
参考書籍
参考記事
Sinatraについては、こちらの記事を参考にさせていただきました。
- Rubyの入門や書き捨てアプリを作る場合は sinatraがオススメ! - むかぁ~ どっと こむ
- SinatraとjQueryでおよそ100行で作るAjax掲示板アプリケーション - gaaamiiのブログ
- Sinatra: README (Japanese)
関連記事
- 恋するプログラムを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[おわりに]