2020/11/09
というわけで、今回は「ランパの台詞bot」制作その3です!
少しずつ作っていた台詞集ですが、ゲーム開始からエンディングまでの台詞を集め終わりました!
前回同様、改良点や苦労した点を載せていきます。
ちょっと長めの記事ですが、今回でほぼ完成までいけた・・・かと思います!
前回の記事は↓になります!
目次
ツイートした台詞を一定期間再ツイートしないようにする
かなりレパートリーは豊富になりましたが、それでも短期間に連続してツイートしてしまうことがあります。
以下は1月19日のツイートです。
ゴマキューがいってたみたいに、あのまるい石、
氷ソーダのいずみのエネルギーがあつまってできたものみたいだね
【ステージ3-3】— スタフィー_ランパ台詞bot (@rampa_bot) 2017年1月19日
ゴマキューがいってたみたいに、あのまるい石、
氷ソーダのいずみのエネルギーがあつまってできたものみたいだね
【ステージ3-3】— スタフィー_ランパ台詞bot (@rampa_bot) 2017年1月19日
種類が豊富なのに短期間で同じ台詞を言わせるのはいただけないです。
そんなわけで、1回ツイートした台詞は少し期間を空け、連続でツイートしないようにします。
これを実現するにはサーバ側でデータを取っておくか、過去のツイートを取得して判定するしかありみあせん。
幸い、WordPress用にMySQL
が入っているので、それを使ってサーバ側で判定してみます。
当初「台詞をデータベースで管理する」ことは想定していなかったので、台詞ごとにシーケンシャルなIDなんていいものはありません。
当然ながら一意に識別できるコードなんてものもありません。
そこで使用するのがハッシュ値を計算するmd5()
です!
これで台詞をハッシュ値に変換し、識別しやすい形に置き換えて保存します。
普通に台詞を格納するのも手ですが、最大130文字近くになる台詞がある上、マルチバイト文字をそのまま入れて比較・・・というのは避けたかったためです。
下準備として、データベースとテーブルを作成しておきます。
端末上から直接作ってしまいましょう。
CREATE DATABASE twitter_bot; use twitter_bot; CREATE TABLE rampa_tweet_history ( id INT PRIMARY KEY AUTO_INCREMENT NOT NULL, message TEXT NOT NULL, create_date DATETIME NOT NULL );
データベース名やテーブル名は適当です。
id
は不要かもしれませんが、一応作っておきます。
create_date
は一定期間以上経過した台詞を弾くために必要です。
前回作成したPHPファイルを修正していきます。
それなりに長くなりましたが、以下がソースになります!
<?php require_once("twitteroauth-master/autoload.php"); require_once('twitteroauth-master/src/TwitterOAuth.php'); use Abraham\TwitterOAuth\TwitterOAuth; /** * ランパの台詞集から台詞を抽出・ツイートするクラス */ class TwitterRampaBot { private $customer_key; private $customer_secret; private $access_token; private $access_token_secret; private $db_user = 'db_user'; private $db_password = 'db_password'; private $interval_spec = 'P3D'; const file_path = 'rampa_serifu.txt'; const max_tweet_len = 140; /** * コンストラクタ */ public function __construct() { $this->customer_key = "customer_key"; $this->customer_secret = "customer_secret"; $this->access_token = "access_token"; $this->access_token_secret = "access_token_secret"; mb_internal_encoding('UTF-8'); } /** * つぶやきを送信する */ public function tweet() { $message = $this->getTweetWord(); // 文字数チェック if (self::max_tweet_len < mb_strlen($message)) { trigger_error('ツイート可能な文字数を超えています。', E_USER_ERROR); exit; } // サーバにつぶやきを送信 $connection = new TwitterOAuth($this->customer_key, $this->customer_secret, $this->access_token, $this->access_token_secret); $connection->post("statuses/update", array("status" => $message)); } /** * ツイート用の文字列を取得・整形する * * @return string */ private function getTweetWord() { // テキストファイルから文字列読み込み $input_lines = file(self::file_path); $words = array(); // データベース接続 try { $dsn = 'mysql:host=localhost;dbname=twitter_bot;charset=utf8'; $pdo = new PDO($dsn, $this->db_user, $this->db_password); } catch (\Exception $e) { trigger_error('データベースへの接続に失敗しました。', E_USER_ERROR); exit; } // 先頭 # の行を取得するまでループ while (true) { $line_num = rand(0, count($input_lines)); // 先頭が # で始まる行を抽出 if (isset($input_lines[$line_num]) && preg_match('/^\# .*$/', $input_lines[$line_num])) { $words = array(); // # 以降を場面として取得 $scene = $this->excludeLineFeed($input_lines[$line_num]); $scene = str_replace('# ', '', $scene); // 複数行の台詞を全て取得するまでループ for ($i = $line_num + 1; ; $i++) { // 文字列か空or改行コードのみの場合は終了 if (empty($input_lines[$i]) || preg_match('/^' . PHP_EOL . '$/', $input_lines[$i])) { break; } $words[] = $this->excludeLineFeed($input_lines[$i]); } // 台詞と発言場面を設定 $message = ''; foreach ($words as $word) { $message .= $word . PHP_EOL; } $message .= '【' . $scene . '】'; // 台詞が一定期間内にツイートされているかチェック if (!$this->checkMessage($pdo, $message)) { continue; } // ツイートする台詞を保存 $this->insertMessage($pdo, $message); break; } } $pdo = null; return $message; } /** * 台詞がデータベース内に保存されているかチェックする * * @param PDO $pdo * @param string $message */ private function checkMessage(PDO $pdo, $message) { $sql = 'SELECT count(id) FROM rampa_tweet_history WHERE message = :message AND create_date > :create_date'; $date = new DateTime(); $date->sub(new DateInterval($this->interval_spec)); $pdo_stat = $pdo->prepare($sql); $pdo_stat->bindParam(':message', md5($message)); $pdo_stat->bindParam(':create_date', $date->format('Y-m-d H:i:s')); $pdo_stat->execute(); // ツイートされていた場合は台詞を再取得 if (0 < $pdo_stat->fetchColumn()) { return false; } return true; } /** * 台詞をデータベースへ挿入する * * @param PDO $pdo * @param string $message */ private function insertMessage(PDO $pdo, $message) { $sql = 'INSERT INTO rampa_tweet_history (message, create_date) VALUES (:message, :create_date)'; $date = new DateTime(); $pdo_stat = $pdo->prepare($sql); $pdo_stat->bindParam(':message', md5($message)); $pdo_stat->bindParam(':create_date', $date->format('Y-m-d H:i:s')); $pdo_stat->execute(); } /** * 指定した文字列から改行コードを除外する * * @param string $str * @return string */ private function excludeLineFeed($str) { return str_replace(PHP_EOL, '', $str); } } $twitterRampaBot = new TwitterRampaBot(); $twitterRampaBot->tweet(); exit; ?>
※ツイートの取得や送信部分は前回の記事をご参照下さい。
MySQLへの接続にはPDO_MySQL
を使用しました。
明確な理由はありませんが、ガチガチに書くわけでもないので、PDOが手っ取り早いかなーと。
仕事ではDB関連はフレームワークに任せてしまうので、別の意味で苦戦していたりもします。
ポイントはcheckMessage()
内の処理です。
一定期間はinterval_spec
で定義し、P3D
を設定しておきます。
これをDateTime::sub()
で指定することで、「現在時間から3日前の日時」になります。
このあたりの指定方法は公式マニュアルに書いてあります。
http://php.net/manual/ja/dateinterval.construct.php
「データベース内に台詞が存在するか」だけ欲しいので、実行するSQLはSELECT COUNT(id)
です。
execute()
実行後、fetchColumn()
を実行することでレコード数を取得することができます。
あとはif
で評価して真偽を返し、false
であればcontinue
で再度台詞を取得します。true
であれば台詞をハッシュ化してデータベースに保存します。
作りたての状態で実行すると、データベース接続が出来なかったり、日付型の比較でしくじったりするので、事前にテストを行うことをおすすめします。
端末上から直接PHPファイルを指定して実行すればOKです。
その場合、「サーバにつぶやきを送信」の部分をコメントアウトし、echo
やvar_dump()
を使用しましょう。
テスト用として別ファイルで作ってしまうと安全です。
乱数の生成方法の変更
そもそもPHPのrand()
はいろいろと問題があるようで、これを使用していること自体がまずいみたいです。
より良い乱数生成と数倍の速度を持つというmt_rand()
で置き換えます。
使い方は変わらないようなので、本当に置き換えるだけ!
$line_num = mt_rand(0, count($input_lines));
「より良い乱数」になったかどうかはまだ分かりません。
ただrand()
では特定の台詞のみ偏って選出されたりしたので、そのあたりの改善が期待できる・・・かもしれません。
上で記載した「一定期間内にツイートした台詞は除外」機能と合わせれば、少なくとも同じ台詞が続いてしまうことは避けらます。
データベースやPHPのモジュールにもよりますが、気になっている方は試してみてはいかがでしょうか。
苦労したこと
前回に引き続き、bot製作に関して苦労したことを紹介します。
抜けが無く収集できているか心配になる
抜けがないように収集したつもりですが、やはり不安な部分もあります。
例えばランパの変身が可能なエリアで、且つ該当の変身をまだ取得していない場合に特殊な台詞があります。
今のボクのチカラじゃここではへんしんできないみたいだ・・・ ぼうけんしてフシギな石を見つけたら、またへんしんできるようになるかも・・・? 気になるところがあるかもしれないけれど、ひとまず今は先にすすんで、あとでここにもどってみようよ 今のボクのチカラじゃここではへんしんできないみたいだ・・・ ぼうけんしてフシギな石を見つけたら、またへんしんできるようになるかも・・・? ひとまず今はちがうみちをすすんでみて・・・あとでここにもどってみようよ、スタフィー
この際、場所によって微妙に台詞が違ったりします。
自分は後半で気付きましたが、もしかしたら前半の隠しエリアなどにも設定されているかもしれません。
その場合、ストーリーを最初からやり直すことになりかねません。
手動で収集・入力することに限界を感じる瞬間ですが、メインとなるストーリー上のイベントはすべて搭載しているので、台詞botとしては妥協ラインかなーとも思います。
裏を返せばそれだけの台詞パターンがある位、ゲーム自体の作りが丁寧ということですね。
攻略本で変身するエリアだけを確認する等、漏れがないような施策を考えていきたいところです。
かなり長い台詞がある
例として、以下はステージ8-3後半の会話です。
途中キョロスケが1~2回話す以外、ほぼランパのみが喋り続けます。
す、スタフィー・・・なかまが・・・なかまが・・・ みんなっ、しっかりっ!!へんじをしてーーー!! ダメだ・・・みんなチカラをダイールにすいとられてうごけないみたい・・・ ダイール・・・何てことを・・・ ゆるさない!ゆるさないぞーぉおお!!! 何があっても・・・みんなをもとの元気なすがたにもどすんだ!!! ボクをダイールからまもるために、みんなはぎせいになった・・・ みんなは・・・なかまは・・・かぞくどうぜんなんだ ボクには・・・パパもママもいないから・・・みんながかぞくなんだ! ・・・うん、ボクが生まれてすぐ・・・パパもママもしんじゃったんだって すごく小さかったからよくわからなかったけど さみしくないようにって、みんながやさしくしてくれた ボク、こうやってスタフィーたちとであえてわかったんだ スタフィーも王子、ボクも王子、おなじ王子なのに、スタフィーはいつもボクをたすけてくれた このままじゃいけないんだ!スタフィーのように、強くやさしく・・・みんなをまもれる王子にならなきゃ! ねぇ、スタフィー ボクやるよ! ダイールをたおして・・・なかま・・・かぞくをとりもどすんだ!!! ぜったいこのままでおわらせるわけにはいかない!さあ!行こう!
台詞自体はランパの過去や成長、決意が見て取れる大変よろしいものです。
ただし、bot的には「ツイート可能な文字数は140まで」という制限に引っ掛かります。
仕方ないので文としての意味合いが切れない程度に分けていますが、それでも140文字以内に収まるか怪しくなる台詞もあります。
上の例で言えば、「ボク、こうやってスタフィーたちと~」から3行で129文字です。
この例に限らず、稀に非常に長い台詞があります。(7-4など)
こればかりはどうしようもないので、やはり文としての意味合いを保ちつつ分けています。
一部強引な分け方をしていますが、「こんな台詞がある」というbotであり、所謂「名言集」ではないので・・・と言い訳しておきます。
余談ですが、その割にはエンディングは超あっさりで、ダイール撃破後にランパが発した明確な台詞は以下の2つのみです。
スタフィー!だいじょうぶ? スタフィー!ほんとうにありがとう またあそびにきてね
後者はスタッフロール後の一番最後の台詞で、「また遊んでね!」的な意味合いが含まれていると思われるため、実質1つのみです。
まあエンディングなのでナレーションベースということもありますが、ちょっと驚きました。
「ランパのあつめもの」の存在を忘れる
キョロスケ様のかばんメニューに「あつめもの」があります。
このメニュー、ストーリーの進行具合に応じてランパのコメントがあります。
が、このメニュー自体の存在をステージ5まで忘れていました。
更新タイミングは(たぶん)ステージクリア時とエンディング後なので、ステージ5からメモっていけばやり直す範囲も抑えられますが、完全にノーマークだったので精神的ダメージが大きかったです。
ちなみに「今日のゲスト」の各台詞と、クリア後にあつめものを見た際の「ランパのにちじょう」シリーズはすべて回収しました。
ただし、「ランパのにちじょう (海)」だけ異常に出現率が低く逃しそうになりました。
内容は上画像の通りですが、さらっとすごいことを言っているので意図的に低確率なのかもしれません。
隕石を弾き返すほどの回転をランパが習得できるとは思えませんが・・・。
「キョロスケ様のキャラ診断」なるフラッシュがある
公式サイトに「キョロスケ様のキャラ診断」なるものがあります。
公式自体見たことがなかったので、海外のフォーラムで初めて知ったりもします。
現在でも公式サイトにリンクがありますが、画面下部にひっそりと表示されているだけです。
気合でランパっぽくなるような選択肢を選びます。
やはりというか、キャラクター毎のコメントがあります。
ゲーム外の台詞ですが、「ゲーム内の」と限定して作っているわけでもないので、とりあえず台詞集に追加しておきました。
ちなみにハマグリ曰く、「真面目で素直な優等生タイプ」らしいです。
まとめ
そんなわけで、ランパの台詞bot作成(ほぼ)完了です!
プレイしていると、キャラクターやストーリーが懐かしく感じて、意外とハマってしまったりします。
ぼ・・・ボクも、スタフィーにまけないように、りっぱな王子にならなくっちゃ!
【ステージ8-2】— スタフィー_ランパ台詞bot (@rampa_bot) 2017年2月11日
現在の台詞パターンは193種類です!
1アクションゲームとしてみると相当な量ではないでしょうか。
あとは上述した「あつめもの」をステージ1~4分入れれば完成かと思います!