entries

麻雀サーバーゴーストを作った

 - by Don

外部アプリとしての麻雀ゲームからのイベント通知を想定した麻雀クライアントゴーストは以前作ったのですが、双方向での通信(何を切るかの問い合わせ等)がやりたかったのでゴーストで麻雀ゲームを作ることにしました。まだ七対子の形式しか対応していませんが、クライアントゴーストとの双方向のやり取りは上手く動いているようなのでキリの良いところで一度晒そうと思い立った次第です。

雀々BARIBARI
麻雀サーバーゴースト。製。まだ七対子しか対応していない。
雀-リンリン
麻雀クライアントゴースト。YAYA製。何を切るかを自身で思考する。
雀-チュンチュン
麻雀クライアントゴースト。華和梨製。何を切るかを自身で思考する。
雀-ホワンホワン
麻雀クライアントゴースト。里々製。何を切るかはサーバーにお任せする。

まだおよそ麻雀と呼べるような代物ではありませんが、七対子しか対応していないが故にかえって麻雀を知らない人の方が楽しめるかもしれません。

ゴースト同士ということで通信に\![raiseother]を使用していますが、昨日の記事にある通り、今後はDirectSSTPを使用した外部アプリケーションとの情報のやり取りにも対応できるよう柔軟な仕様の策定を見据えていきたいです。

夢はどこまでも広がりますが、まずは普通の形式(七対子以外)で和了れるようにしないとですね。

DirectSSTPでWindowsアプリとゴーストが会話できるようになった

 - by Don

SSP 2.5.03 – ばぐとらぶごる

+ "X-SSTP-Return-" ではじまるSHIORIからのレスポンスヘッダをSSTPのレスポンスにそのまま横流しする実験的仕様
例: X-SSTP-Return-Hoge: hogehogehoge

SSP 2.5.05 – ばぐとらぶごる

※ "X-SSTP-Return-" を "X-SSTP-PassThru-" に改名し、レスポンスだけではなくリクエストヘッダにも追加できるようにした。

SSTPを送信した側が、ゴーストからのメッセージを受け取ることができるようになりました。

応用例はいくつか考えられます。

  • 今までは音楽再生アプリが「再生している曲」を通知することしかできなかったが、「再生したい曲」を問い合わせることができるようになった
  • 今までは麻雀ゲームアプリが「何を切ったか」を通知することしかできなかったが、「何を切るか」を問い合わせることができるようになった
  • 今まではスケジュール管理アプリが予定を通知することしかできなかったが、ユーザとのデートの予定を勝手に入れられるようになった

ゴーストがますます主体的に「意思」を持つようになってくるかもしれません。

具体的にどうやるの

DirectSSTP送信テストツールとレスポンスを返すサンプルゴーストを作ってみました。

動いている様子

まだ実験的段階ですし

ゴーストからのレスポンスを利用したアプリがまだこの世には存在していないので焦って対応を考える必要はありません。ただ、ゴーストからのレスポンスを利用したいと考えるアプリケーション作者さんの目に止まればという願いを込めて、サンプルを公開いたしました。どうぞご活用ください。

ゴースト「箱入り娘」公開

 - by Don

箱入り娘

ダウンロード:箱入り娘

箱入り娘 (パズル)で遊ぶことができるゴーストです。ゴーストマスカレード3で提出したゴーストの正式公開版となります。ネットワーク更新に対応しております。バグ等があれば修正していく所存でおります。

仕様

スタンプ帳対応

プラグイン「スタンプ帳」に対応しています。クリアするとスタンプがもらえます。

イベント通知

箱入り娘は、同時起動中のゴーストに対してイベント通知をしています。

ID: OnKlotski
Reference0: バージョン (例:"klotski/0.1")
Reference1: コマンド   (例:"BROADCAST")
Reference2: コマンドID (例:"status")
Reference3: 1行目      (例:"祖母,祖父,母親,父親")
Reference4: 2行目      (例:"祖母,祖父,母親,父親")
Reference5: 3行目      (例:"華道,書道,兄弟,兄弟")
Reference6: 4行目      (例:"空白,娘,娘,和裁")
Reference7: 5行目      (例:"空白,娘,娘,茶道")
Reference8: step       (例:"116")

これにより同時起動中のゴーストは駒の配置を認識することができます。

外部から実行できる関数

辞書に定義されている関数はそのままの名前で実行できます。(ランダムに動かす例:\![raiseother,箱入り娘,moveRandom])

引数が必要な関数は、(int型やstring型であれば)Referenceを指定することで実行できるようにしたOnKlotski関数を用意しています。(娘を右に動かす例:\![raiseother,箱入り娘,OnKlotski,klotski/0.1,EXECUTE,function,moveRight,娘])

その他

作者がこのパズルを知ったのは今年の1月です。以下のTweetがたまたまTLに流れてきました。

わりと有名なパズルなのですね。知らなかった。機会があれば遊んでみたいと思っていたのですが、ゴーストマスカレード3が企画されたことで、機会は自分で作ればいいと思い、制作に至りました。自分で作ったクセに未だクリアできていません。助けて。

追記

ゴースト「しぇるずかん」公開

 - by Don

しぇるずかん

ダウンロード:しぇるずかん

フリーシェルWikiを閲覧できるゴーストです。ゴーストマスカレード3で提出したゴーストの正式公開版となります。ネットワーク更新に対応しております。バグ等があれば修正していく所存でおります。

イベント提出版からの変更点

  • 「全取得」メニューの上限値を20件から50件へ緩和
  • すべてのメニュー階層に「戻る」を追加(シェル個別ページは現在絞り込み中の作者名または属性名のアンカーを赤文字で表示することで「戻る」への代替とした)

ごーとうさんから改善案を頂けたことにより我ながら「前より使いやすい!」ってなりました。ありがとうございます。

ごーすとじてん

ダウンロード:ごーすとじてん

上記追加シェル:ねこぴょんかんけい3

2007年に作ったゴーストですが、まだなんとか動いているようなので、もしよろしければこちらもセットでどうぞ。

メニューから「format ghost data」を実行してください。完了するとインストール済のゴースト一覧が閲覧できます。「♪」マークでゴーストをコールできるのでゴーストエクスプローラ的な使い方ができるかと思います。

しぇるずかん同様、マウスホイールでパラパラとページめくりができるので使い方によっては便利かと思います。

おまけ

特殊な対応が施されたゴーストからは追加情報を読み取ることができます。(画像:ゴースト「アマイロ」)

ghostbook-page-of-amairo

ついっとゅう更新 Ver4.6

 - by Don

YAYA(yaya.dll)から灯(akari.dll)への移行

ついっとゅうを更新しました。

しばらく放置したせいでTwitterの仕様変更に追随できていなかった点がいくつかあったのでその修正をしました。

また、DLLをYAYAからに差し替えました。この影響でセーブデータの引き継ぎがされないため、お手数ですが設定画面からついっとゅうの認証画面へ飛んで以下のパラメータを再入力してください。

  • oauth_token
  • oauth_token_secret
  • API Proxy URL

必要に応じて新着通知間隔も設定してください。

ゴーストの好感度の取得

 - by Don

これを見て思いついた実装がこんなイメージ。

最初はプラグインで作ろうとしたのですが色々と障壁が多すぎて断念しました。素直にOnRequestValues/OnGetValuesを使ってごーすとじてんのデータ収集機能に含めることにしました。こういうメタ情報を扱うならこのゴーストに"似合う"だろうし。

双方向でないのはバルーンに右寄せして重ねるのが大変だったからという理由でしかありません。

以下、今回取得対象としたプロパティ名です。(※里々での対応)

#-----------------------------------------------------------
# OnRequestValues対応
#-----------------------------------------------------------
@OnRequestValues
\![raiseother,(R0),OnGetValues,(FMO0name)(loop,AddRVArg,1,(Rの数))]
@AddRVArg
(call(バイト値,1)AddRVArgExist_(単語群「(R(AddRVArgカウンタ))」の存在)(バイト値,1)(R(AddRVArgカウンタ)))
@AddRVArgExist_1
,(A0)=((A0))
#-----------------------------------------------------------
# 好感度プロパティ
# 存在しない場合は無くて良い
#-----------------------------------------------------------
#\0の\1に対する好感度 0~10までの値にしておくとバルーン内に収まるハズ
@01好感度
1
#\0の\2に対する好感度
@02好感度
2
#\0のuserに対する好感度
@0user好感度
3
#以下同様
@10好感度
4
@12好感度
5
@1user好感度
6
@20好感度
7
@21好感度
8
@2user好感度
9
#好感度を表示する際にバルーンに表示する\0のアイコンの絶対パスまたは相対パス
#相対パスは送信先から見たものなので上の階層に上がってから降りてくる
#大きさはバルーンサイズ的に30pxくらいが丁度いいと思う
@0好感度アイコン画像パス
..\..\..\your-ghost-directory\ghost\master\image\ca0.png
@1好感度アイコン画像パス
..\..\..\your-ghost-directory\ghost\master\image\ca1.png
@2好感度アイコン画像パス
..\..\..\your-ghost-directory\ghost\master\image\ca2.png
@user好感度アイコン画像パス
..\..\..\your-ghost-directory\ghost\master\image\causer.png
#特殊プロパティ
#通常">"のところを別のマークに変える 以下はハートマークのUnicode
@0user好感度マーク
\_u[0x2665]

ごーすとじてんの各ゴーストページ上部プロフィールボタン「P」の左に新しく追加した「C」ボタンから確認できます。

うかてんにイベント通知とゴーストから利用できる関数を追加

 - by Don

天気予報プラグイン「うかてん」を更新しました。

  • ゴーストへのイベント通知追加
  • ゴーストから利用できる関数追加

ゴーストへのイベント通知追加

プラグイン実行時にGhost側に通知されるイベントは以下の通りです。

ID: OnPluginExec
Reference0: バージョン (例:"Version=ukaten/0.3")
Reference1: 送信元     (例:"From=うかてん")
Reference2: Script     (例:"Script=\0\s[0]Hello, world!\e")
Reference3: イベント名 (例:"Event=OnChoiceSelectEx")
Reference*: Reference3のイベントに対応するReference。
            Reference3とそれ以降を、SHIORIイベントそのもののように使う。

選択肢などPlugin内部で定義したReferenceもそのまま通知されています。拙作の「ついっとゅう」と互換仕様で、内部の情報をただ垂れ流しているだけです。

ゴーストから利用できる関数追加

こっちが本題。今回は3つの関数を追加しました。

OnGetPluginVersion

プラグインのバージョンを取得
[戻り値]
Reference0: プラグインのバージョン("ukaten/0.3"等)

使用例

*うかてんのバージョン確認
:\![raiseplugin,うかてん,OnGetPluginVersion]

*OnGetPluginVersion
:(R0)

OnGetSavedArea

ユーザが設定した地域のIDと表示名を取得
[戻り値]
Reference0: 地域ID(東京都の場合は"130000")
Reference1: 地域名("東京都"等)

使用例

*ストーキング開始
:\![raiseplugin,うかてん,OnGetSavedArea]

*OnGetSavedArea
$ユーザの住むところ【タブ】(R1)
:(ユーザ名)さん…。
私、来月(ユーザの住むところ)に引っ越すんだ…。
これからは毎日会えるね…。フフフ…。

OnGetWeatherInfo

気象庁から天気予報を取得(※利用時は出典が気象庁である旨を明示してください。)
[引数]
Reference0: 取得情報の種類("forecast","overview_forecast","overview_week")
            省略時は"overview_forecast"
Reference1: 地域ID(東京都の場合は"130000")
            省略時は保存しているユーザ設定値を使用
Reference2: 取得情報のキー("publishingOffice","reportDatetime","targetArea","headlineText","text"等)
            省略時は"text"
[戻り値]
Reference0: 取得情報

使用例

*天気予報開始
:\![raiseplugin,うかてん,OnGetWeatherInfo]

*OnGetWeatherInfo
:(ユーザ名)、天気予報だよっ!

『(R0)』

って気象庁ホームページに書いてあったよ!

\![raiseplugin,うかてん,OnGetWeatherInfo,overview_week]とすると週間天気予報になったり、\![raiseplugin,うかてん,OnGetWeatherInfo,,,headlineText]とすると短く要約したテキストになったりします。(気象庁側で準備されていなかったりする地域もあり、その場合は空文字になります)

ゴーストと天気予報と灯(akari.dll)のお話

 - by Don

気象庁の中の人より、非公式ながらAPIっぽいものが利用できるとの発信がありました。

天気予報といえば実に伺かっぽいです。天気予報の機能があるゴーストさんもそれなりに見受けられた気がします。しかしおそらく大体のゴーストはどこかのWebページをスクレイピングしていたのではないかと想像します。しかしこの非公式APIを利用できればもう少しまともな実装ができそうです。

JSONパーサSAORIを求めて

2021-03-22追記。黄桃の誄歌-Etu-様にて「YAYA as SAORI jsonパーサ」が公開されております。追記ここまで。

この非公式APIはJSON形式で出力しているようです。ゴーストから利用するにはJSONを解析したいところです。JSONが扱えるSAORIを探してみましたがなかなか見つかりません。しかしJSONを扱えるSHIORIを見つけました。

夜天燈火

灯(akari.dll)という名のSHIORIで、ドキュメントを見るに、辞書型の変数をサポートしているようです。そして_json2azvという内部関数を使えば辞書型や配列型の変数として読み込んでくれると。また_customrequest関数を独自に定義することでSAORIとして使用することも可能なようです。

ワクワクが止まりません。もはや天気予報などそっちのけでこのSHIORIへの興味が湧いてきました。

サンプルゴーストの639C 灯花さんの中身を見て構造を理解しようと思っていたら、サンプルとしてこんな機能が用意されていました。

akari-weather-forecast

気象庁のWebページをスクレイピングして天気予報してる…。運命に導かれたかのような錯覚に陥ります。

do-you-wanna-do-like-this

「こういうことがやりたいんでしょ?」と言われているような気分です。いや、当初はそうだったけど。今は興味の対象は貴女なのです。

しかも現在の気象庁のWebページは昔と構造が変わっており天気予報機能は正常に動いておりません。スクレイピングの宿命ですね。冒頭の非公式APIを読み取るように変更すれば動きそうです。

fix-bug-if-you-want-akari

ああ、そうか。何と素晴らしいサンプルゴーストなのでしょう。灯の使い方を指南してくれるばかりでなく、練習問題まで用意してくれていたのですね。やってやろうじゃないの。

ghost\master\res\util.azr を以下のように書き換えます。

// 地域を選択させるサクラスクリプトを返します。
string weather_news_arealist()
{
	string	strAreas;
	array	arKeys	=	_dickeyget( dictWeatherAreas );

	strAreas	+= "\_q";
	for( int i=0; arKeys[i]!=nil; i++ )
	{
		if( i%2 == 0 )	strAreas += "\n";
		strAreas	+= "\![*]\q["+arKeys[i]+",OnWeatherNewsAreaSelected,"+arKeys[i]+"]  ";
	}

	return strAreas;
}

// weather_news_arealist()によるサクラスクリプトから、地域を選択した時に呼ばれます。
OnWeatherNewsAreaSelected( dict ref )
{
	_create_thread( "TH_OnWeatherNewsAreaSelected", ref["Reference0"] );
}

// OnWeatherNewsAreaSelectedから別スレッドで起動される関数です。
// 天気予報を取得してキャラクターに喋らせます。
TH_OnWeatherNewsAreaSelected( string strArea )
{
	dict	dictWN	=	weather_news( strArea );
	
	string	strTalk	=	"\0\s[0]\b[2]"+strArea+"の天気は…\n";
	strTalk	+=	dictWN["text"];
	strTalk	+=	"\n\n(※出典:気象庁ホームページ)\e";

	_speak( strTalk );
}

/*
weather_news
・この関数の結果を使用するときは、「出典:気象庁ホームページ」を記載してください。
・引数であるstrAreaNameには転記を知りたい地域の名前を指定して下さい。
 指定できる地域の名前は、dictWeatherAreasという辞書型変数のキー値です。
・dictWeatherAreasはこの関数の後ろにグローバル変数として定義されています。
*/ 
dict weather_news( string strAreaName )
{
	string strAreaFileName = dictWeatherAreas[strAreaName];
	if( strAreaFileName == "" ) strAreaFileName = dictWeatherAreas["東京都"];
	
	array	arHtml = _httpget( "https://www.jma.go.jp/bosai/forecast/data/overview_forecast/"+strAreaFileName+".json", "utf8" );
	dict	dictWeather;
	dictWeather = _json2azv(arHtml);
	
	return dictWeather;
}
	dict dictWeatherAreas = ${
		$("宗谷地方", "011000" ), $("上川・留萌地方", "012000" ),
		$("網走・北見・紋別地方", "013000" ), $("釧路・根室地方", "014100" ),
		$("胆振・日高地方", "015000" ), $("石狩・空知・後志地方", "016000" ),
		$("渡島・檜山地方", "017000" ), $("青森県", "020000" ),
		$("秋田県", "050000" ), $("岩手県", "030000" ),
		$("山形県", "060000" ), $("宮城県", "040000" ),
		$("福島県", "070000" ), $("茨城県", "080000" ),
		$("群馬県", "100000" ), $("栃木県", "090000" ),
		$("埼玉県", "110000" ), $("千葉県", "120000" ),
		$("東京都", "130000" ), $("神奈川県", "140000" ),
		$("山梨県", "190000" ), $("長野県", "200000" ),
		$("新潟県", "150000" ), $("富山県", "160000" ),
		$("石川県", "170000" ), $("福井県", "180000" ),
		$("静岡県", "220000" ), $("岐阜県", "210000" ),
		$("愛知県", "230000" ), $("三重県", "240000" ),
		$("大阪府", "270000" ), $("兵庫県", "280000" ),
		$("京都府", "260000" ), $("滋賀県", "250000" ),
		$("奈良県", "290000" ), $("和歌山県", "300000" ),
		$("島根県", "320000" ), $("広島県", "340000" ),
		$("鳥取県", "310000" ), $("岡山県", "330000" ),
		$("香川県", "370000" ), $("愛媛県", "380000" ),
		$("徳島県", "360000" ), $("高知県", "390000" ),
		$("山口県", "350000" ), $("福岡県", "400000" ),
		$("佐賀県", "410000" ), $("長崎県", "420000" ),
		$("熊本県", "430000" ), $("大分県", "440000" ),
		$("宮崎県", "450000" ), $("鹿児島県", "460100" ),
		$("沖縄本島地方", "471000" ), $("大東島地方", "472000" ),
		$("宮古島地方", "473000" ), $("八重山地方", "474000" )
	};

これで無事に天気予報ができるようになりました。

weather-forecast-fixed

改善点があるとすればdictWeatherAreasの内容もベタ書きするのでなく非公式APIからリストとして取得してくるのが良さそうですが、今回はここまで。習作としてsupplementのNARにしておきます。自分が書いたコード部分のライセンスはパブリックドメインです。

639C_tomoka_supplement.nar

参考

新しい気象庁サイトからJSONデータが取得できる件 | MindTech

ゴーストからTwitterに投稿する(其の参―中級編)

 - by Don

目次

準備

上級編を見てわかる通り、普通にやると準備が大変です。なので中級編では筆者が準備した環境を中継して投稿する方法を紹介します。

「概要」で説明した通り、以下のものが必要になります。

  • BOT用Twitterアカウント
  • Twitter APIの認証キー(2つ)※Twitter開発者登録は不要
    • ACCESS_TOKEN(アプリに対しアカウント毎に割り当てられる)
    • ACCESS_TOKEN_SECRET(アプリに対しアカウント毎に割り当てられる)
  • PHPが使用可能なサーバ(PHP7でなくても可)

BOT用TwitterアカウントとPHPが使用可能なサーバは例によって各自用意してください。

Twitter APIの認証キー(2つ)は以下のリンクから取得してきてください。

UKABOT

適当な名前のPHPファイルを作成してサーバにアップロードします。今回はsample.phpとしました。このファイルにコードを記述していきます。

<?php
define('ACCESS_TOKEN', 'XXXXXXXXXXXXXXXXXXXX');
define('ACCESS_TOKEN_SECRET', 'XXXXXXXXXXXXXXXXXXXX');

$url = 'http://nikolat.starfree.jp/rentalbot/sendtweet.php';
$mes = 'サーバから送信テスト';
$post_data = array('access_token' => ACCESS_TOKEN, 'access_token_secret' => ACCESS_TOKEN_SECRET, 'status' => $mes);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url); 
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
$res =  curl_exec($ch);
curl_close($ch); 

header('Content-Type: text/plain; charset=UTF-8');
print 'おしまい';

文字コードはUTF-8にしておきましょう。XXXの部分は自身が取得した2つのAPIの認証キーに書き換えてください。これを保存してWebブラウザからこのphpファイルがあるURLにアクセスしてみましょう。ブラウザには「おしまい」と表示され、BOTのアカウントに「サーバから送信テスト」と投稿されるはずです。

今はWebブラウザからアクセスしましたが、最終的にはSSPからアクセスし、メッセージを転送することを目標としています。

送信するテキストの部分を以下のように書き換えましょう。

<?php
define('ACCESS_TOKEN', 'XXXXXXXXXXXXXXXXXXXX');
define('ACCESS_TOKEN_SECRET', 'XXXXXXXXXXXXXXXXXXXX');

$url = 'http://nikolat.starfree.jp/rentalbot/sendtweet.php';
$mes = $_POST['message'];
$post_data = array('access_token' => ACCESS_TOKEN, 'access_token_secret' => ACCESS_TOKEN_SECRET, 'status' => $mes);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url); 
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
$res =  curl_exec($ch);
curl_close($ch); 

header('Content-Type: text/plain; charset=UTF-8');
print 'おしまい';

そしてSSPから以下のSakuraScriptを入力します。

\![execute,http-post,【URL】,--param=message=SSPから送信テスト]

【URL】は先ほどWebブラウザからアクセスしたphpファイルがあるURLに置き換えてください。BOTのアカウントに「SSPから送信テスト」と投稿されるはずです。

これでゴーストからTwitterに投稿することができました。

エラー処理

上記のコードは最小限の構成になっています。エラー処理くらいは加えたほうが良いでしょう。

<?php
header('Content-Type: text/plain; charset=UTF-8');

define('ACCESS_TOKEN', 'XXXXXXXXXXXXXXXXXXXX');
define('ACCESS_TOKEN_SECRET', 'XXXXXXXXXXXXXXXXXXXX');

$url = 'http://nikolat.starfree.jp/rentalbot/sendtweet.php';
$key = 'message';
if (!isset($_POST[$key])) {
  print 'メッセージのパラメータ名が違います。';
  exit();
}
$mes = $_POST[$key];
$post_data = array('access_token' => ACCESS_TOKEN, 'access_token_secret' => ACCESS_TOKEN_SECRET, 'status' => $mes);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url); 
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
$res =  curl_exec($ch);
if(curl_error($ch)) {
  print curl_error($ch);
} else {
  print $res;
}
curl_close($ch); 

ここで表示されるはずのメッセージはSakuraScriptを実行したゴーストのフォルダ内 ghost\master\var\sample.php に格納されています。

注意事項

ここで解説した方法は筆者のサーバを経由して投稿する方法になります。筆者がサーバの利用継続手続きを失念したり、筆者のTwitterアカウントが凍結されたりすれば動かなくなりますが、泣かないでください。