SVX日記
2025-01-02(Thu) ヴァリアブル・ジョイスティック「JS-2V」をさらに改良
「ジョイスティックを固定する周囲のネジにはステンのトラスネジを奢りたいな」ということで、そのネジを買ってきた。
ホームセンタのバラ売りを買ったのだが、ケース内に長さの違うネジが混在しているのに気づかず、イザ締めようとした時に気づいてビックリ。1本はギリギリの長さだった。確認しなかったのは悪いが、ちゃんと元に戻しとけよな。まぁ、表面からは見えないからこれでよしとする。なんか、レイトレーシングCGっぽいな。ネジの頭にボールが映り込んでいるしw。
トラスネジは表面に頭が突出することを前提に、そこそこ美しさに配慮したタイプのネジだ。んが、実際の業務用の筐体では、内側のフレームにネジ止めするので、表面にはネジ頭が出ないのが正解なのだな。でもまぁ、自分のプレイスタイルでは、さほど邪魔になるわけでもないのでこれでよしとする。
一方で、裏のレバーの8/4方向の切り替えプレートを止めているネジを、手回し可能なものに交換した。実際に使ってみると想像以上に頻繁に切り替えが必要だったので。さほどガッチリと締め上げる必要のないネジなので、手回しで問題ないし。これはだいぶ大きな改良だな。
今回のアケコン製作の動機の大半はドルアーガだったのだが、なんかすっかり腕が落ちていてリハビリ中だ。老化するよりも早く杵柄を確立しなくては。ところで、複数の場所で見かけるのが「ドルアーガの上手い人はカッコいい」という記述だ。ドルアーガを知らない人がそう思う訳はないので、それなりにドルアーガをプレイしてきた人でそう思う人が多い、ということなのだろうが……ムッチャわかるんだよな、これ。何度か書いたが「起きうる理不尽気味な不運を咄嗟の状況判断で可能な限り抑え込んでいく」という、その奇跡のようなゲームバランスを知るからこそ、上手い人にリスペクトを感じてしまうのだ。宝箱の出し方の覚えゲー、という見方は側面のごく一部なのだ。ホント、1984年に出たとは思えない、始祖にしてオーパーツのようなゲームなんだよなぁ。
2025-01-04(Sat) 友人とのやりとりを自動化するXalebot-tmo
先日、亀岡にドライブした夜に「AIとSF」というアンソロジを買って読み始めたのだが、その中の「没友」という作品には唸らされた。人とのやりとりをAIによる代理応答に任せることや、バーチャルに旅行することが当たり前になった時代。旧い友人とのやりとりの末、一緒にバーチャルな旅行に出かけたのだが、カフェで友人が語り出したエピソードから驚愕の事実を知る……という話。
「没友」は「ぼっとも」と読むのだろう。「ボット」と掛けているわけだ。友人への返信を「ボット」に任せるなんて、なんとも失礼な話だが、メールでのやりとりに限れば、AIによる代理応答は既に実用域なんじゃないだろうか。仕事のどうでもいい相手からのメールなんて、AIに勝手に回答させておけばいい。ヘタすりゃ、こっちが死んでいても、相手は気づかずに給料は支払われ続けるかもね、などとカミさんと冗談交じりに話していた……のだが、ん? それって、別に冗談でもなんでもなく、既に実現可能なのでは? 先日に作ったXalebotを少し改造する程度で。名付けて「Xalebot-tmo」。「tmo」は「Thoughtless Mail Operator」の略。「ザレぼっとも」と読む。
# 各プロンプト
@configs[:prompt] = {}
#configs[:prompt][:ex_q] = '以下の文章から、名乗りを含む挨拶部分、引用部分を除き、質問内容だけを抜き出してください。'
#configs[:prompt][:ex_a] = '以下の文章から、名乗りを含む挨拶部分、引用部分を除き、回答内容だけを抜き出してください。'
#configs[:prompt][:is_q] = '以下のような問い合わせがありました。'
#configs[:prompt][:is_d] = '以下の回答を行いました。'
#configs[:prompt][:mk_a] = 'それに対する回答案の作成を%sお願いします。'
@configs[:prompt][:ex_q] = '以下の文章から、引用部分を除き、内容をそのままの抜き出してください。'
@configs[:prompt][:ex_a] = '以下の文章から、引用部分を除き、内容をそのままの抜き出してください。'
@configs[:prompt][:is_q] = '以下のようなメールがありました。'
@configs[:prompt][:is_d] = '以下の返信を行いました。'
@configs[:prompt][:mk_a] = <<MK_A
それに対して返信内容の作成を%sお願いします。
なお、以下の例文に近い口調でお願いします。
「ふるたです。
はいはい、OKですよ。金曜ね。
ヤベシは休暇なのだよね。到着時刻はいつ?
時間によっては、自分がちょっと早退してもいいけど。
呑みは例によって、しげか月姫がいいな。
できることなら、カラオケは呑みより先にしたい。
ちなみに、金曜なら犬川くんは誘えばくるかもしれんが、
例のカラオケ問題があるから、ちと微妙かも。
ということで。
15時に抜けて、ホテルに迎えに行き、カラオケ2時間、
18時頃からしげで焼き鳥、締めはその辺でコーヒーって感じかな。
まー、彼の場合、たぶんそういうんじゃないんだよな。
15時始めだと、一緒に職場抜けるわけにもいかんし、まぁ、今回は誘うのはヤメとくか。
ヤベシがゴリゴリに押すなら考えなくもないけど。
宿は了解しました。そこ、歌のレッスンに行くときに通る道のすぐ脇だわ。」
MK_A
最後、返信内容の作成指示がちょっと長いが、メールの文面を自分っぽくするために、過去に自分が返信した2通のメールの文面を渡しているからだ。今回は、適当に手動で見繕って固定的に与えているが、返信する相手によって、実際に過去に自分が返信したメールの文面を呼び出して渡すことも容易に実装できるだろう。
矢部です。
7/29の10時〜7/30の12時半まで名古屋金山でお仕事でいます。
もしよろしければ、7/30の13時頃からあそびませんか?
遊べるようであれば、7/30の宿泊を取りますので早めにご連絡いただけると嬉しいです。
よろしくお願いします。
ふるたです。
はいはい、OKですよ。7/30ね。
13時から遊べるとのこと、いいですね。ランチでもしながらゆっくり話そうか。
その後、もしよければカラオケでも行きます?できれば最初にカラオケ行きたい派なんだよね。
呑みは夜にでも、いつもの「しげ」か「月姫」がいいかな。
ところで、犬川くんも誘ってみる?でも例のカラオケ問題があるから、ちょっと微妙かもね。
まぁ、ヤベシが強く望むなら考えるけど。
宿泊の件、了解しました。早めに予約しておくと安心だね。
当日を楽しみにしてます。
ではでは。
先に例示した文面に内容が引きずられすぎている傾向はあるが、意図はちゃんと消化されていて特段の矛盾はない。自分(オレ)から見ると文面のクセはちょっと違うが、数十行程度の例示でこの文面なんだからデキすぎレベルだろう。
矢部です。
返事ありがとうございます。
日曜日のホテルは伏見駅近く(ホテルハルミトンブラック)です。
なので、待ち合わせ場所は伏見駅近くがうれしいです。
待ち合わせ時間は13時頃を希望します。
お昼は各自済ませるか、一緒に食べるかどちらにしましょうか?
ふるたです。
はいはい、OKですよ。7/30の13時、伏見駅ね。
宿も了解しました。ホテルハミルトンブラックか、あそこは場所的に便利だよね。
ランチは一緒にしましょうか。お腹も空くだろうし。
伏見の近くに美味しいパスタのお店があるんだ。そこに行ってみない?
その後は、カフェでゆっくりお茶でもしながら話そう。
そういえば、犬川くんも誘おうかと思ったけど、日曜は予定があるかもしれないし、今回は見送っておくよ。
ヤベシがぜひというなら連絡してみるけど。
久しぶりに会えるのを楽しみにしてるよ。
では、当日は13時に伏見駅で待ち合わせましょう。
よろしく!
現実の自分(オレ)には「伏見の近くに美味しいパスタのお店」の心当たりはないが、返信内容としては特段の矛盾はない。よく見るとホテル名のタイプミスまで修正されている。これ、実際に友人の矢部くん(仮名)が受け取っても気づかないだろうな。
矢部です。
お昼を一緒に食べる件は了解しました。
私の方でも2,3見繕っておきます。
その後の予定はノープランなのでお任せします。
大須まで足伸ばすのもありかもしれませんが、暑そうなのでどうですかね。
夜の食事場所はフルタのお任せに甘えてもいいですか?
ふるたです。
ヤベシ、お昼一緒に食べるの了解。
こっちでもいくつかお店をピックアップしておくよ。
午後はノープランとのことなので、涼める場所を考えておく。大須もいいけど、確かにこの暑さは厳しいかもね。
夜の食事場所は任せてくれてOK。良さそうな店を探しておくよ。
宿は伏見のハミルトンブラックね。そこ、よく通る道だよ。
じゃあ、当日は13時に伏見駅で待ち合わせで大丈夫かな?
当日、楽しみにしてるよ。
ちょっと最後の確認が繰り返されているが、明確な確認がなされなかったからか? なんにせよ、許容範囲ではないかと思う。うーむ、実際に代理応答させていれば、約束をすっぽかすことになるが、代理応答としては完走してしまったと言えるんじゃないだろうか。
これ、実際に友人に対して使うつもりはないけれど、こんな簡単な仕掛けであっても、相手側からすれば十分に自分(オレ)が存在しているように見えるわけで、それこそ、こっちが死んでいても気づくことは難しい。それって、ネット上に自分(オレ)が存在していると言っても、もはや過言ではないのではないか。
2025-01-23(Thu) 「アグレッサーズ 戦闘妖精・雪風」サイン本を入手!
例によってIT系の書籍を眺めるのだが……興味の持てるものが少ない。興味のある分野だが自分の知識が少ない場合、まずはネットの情報で学ぼうと思うし、興味のある分野だが自分の知識が十分な場合、本で学ぶ必要はないし、興味のない分野なら、本は必要ない……という考えに至るのが最近の傾向だ。時間の無駄か……と思いつつ、それでも書店では偶発的な出会いもあるからなぁ。
あ。「アグレッサーズ 戦闘妖精・雪風」の文庫版が出たんだ。フムン。お。著者サイン本、なんてのも置いてある。なんだかさすがは丸善てぇ感じだよなぁ。特段、別の値段が書いてあるワケでもないので、定価でエエんかな。でも、もうハードカバーのヤツを発売日近くに買って、既に読んじゃってるからねぇ……。
……帰宅してから、なんかモノスゴくおかしな気分に……な、なな、なんで買わなかったんだよ、オレ!? ……たかだが千円前後で、敬愛する神林長平先生のサイン本が買えるなら、即買いしかねぇだろうによ。雪風が……買えと言っている。うあぁあぁ……大失敗だぜ。つうか、奇しくも明日も出勤予定だし……いや、出勤予定なんてなくったって、わずかな可能性を信じて、開店直後に件の書棚に突撃するほかねぇだろうよ。
しかし、サイン本って……いや、絶対に売ったりしないけど……どのくらいの価値があるんだろう、と、思ってググったら、なんと名古屋の丸善がサイン本の紹介をツイートしているのを見つけてしまった。時刻を見ると昨晩だ。それは、自分の謎のスルー行動の時刻の後だ。これ見たヤツが朝ダッシュしてきたなら、ドグファイトになるとこだった。よかったぁ。
2025-01-25(Sat) DBusで数当てゲーム
まー、保険タイプの仕事つうのは、何も起こらなければ、何もしないでチャリンチャリンなわけだが、まっとうな神経を持っていれば、だからこそ客に何か起こった時には、イザという働きをしなければな、と構えているべきである。
で、起きた。決して、事前にアレコレしていたワケではないので、決してベストな体勢ではないが、そこからは誠心誠意やらせてもらう。どうもDBusがカラんでいるらしい。GUIを下支えする何か、という程度の認識だったが、この機に学んでおくか。なんだか面白そうだし。というわけで、基本的な概念について、学んでみることにした。自分にとって学ぶとは、実際に使ってみることであり、可能な限りそれを何かに役立ててみることである。
DBusの基本については、ネット上にそれなりの情報があるが、ごく簡単にまとめると以下だ。まず、バスという道路がある。1本のシステムバスと、ユーザ毎のセッションバス。道路の脇にはサービスという店舗が並んでいて、オブジェクトパスという看板が掲げられている。店舗にはインターフェイスという職人がいて、メソッドという仕事を受け付ける。
$ dbus-send --session --dest=org.mate.ScreenSaver --print-reply --type=method_call \
/org/mate/ScreenSaver \
org.mate.ScreenSaver.GetActiveTime
uint32 60
セッションバスという道路脇の、org.mate.ScreenSaverというサービスの店舗の、/org/mate/ScreenSaverというオブジェクトパスの看板を見て、org.mate.ScreenSaverというインターフェイスの職人に、GetActiveTimeというメソッドの仕事を依頼したら「uint32 60」という答えを返してくれた、という感じ。つまり、スクリーンセーバ起動までの時間は60秒って知れたわけだ。
$ dbus-send --session --dest=org.mate.ScreenSaver --print-reply --type=method_call \
/org/mate/ScreenSaver \
org.mate.ScreenSaver.SetActive boolean:true
なお、すべての店舗には共通にorg.freedesktop.DBus.Introspectableというインターフェイスの職人がいて、Introspectというメソッドの仕事を依頼できる。依頼すると、自分の店舗の職人と仕事のリストを返してくれる。
$ dbus-send --session --dest=org.mate.ScreenSaver --print-reply --type=method_call \
/org/mate/ScreenSaver \
org.freedesktop.DBus.Introspectable.Introspect
:
<interface name="org.mate.ScreenSaver">
:
<method name="GetActiveTime">
<arg name="seconds" direction="out" type="u"/>
</method>
<method name="SetActive">
<arg name="value" direction="in" type="b"/>
</method>
:
自分はサンプルを作るにしても、何らかの意味を持たせることで学びが深まると思っているので、今回は古典的な「数当てゲーム」を実装してみた。自分にとって「数当てゲーム」は往年のBASIC言語における「hello, world」だ。幾度となく空で打ち込んで動かして遊んだのがコンピュータとの原体験。
#!/usr/bin/env ruby
require 'bundler/setup'
require 'dbus' # bundle add ruby-dbus
class DBusKazuate < DBus::Object
@@range = 10
@@answer = nil
# プレイヤ用のインタフェイス
dbus_interface('jp.itline.test.Kazuate') { # Interface
# ゲームの開始メソッド
dbus_method(:ready, 'out res:s') { # Member
@@answer ||= (rand * @@range).to_i + 1
"I'm thinking of a number between 1 and %d." % @@range
}
# 予想した数へ対応メソッド
dbus_method(:try, 'in guess:i, out hint:s') {|guess| # Member
unless(@@answer)
'Not ready yet...'
else
if(guess < @@answer)
'Too low!'
elsif(guess > @@answer)
'Too high!'
else
@@answer = nil
'Congratulations!'
end
end
}
}
# ゲームマスタ用のインタフェイス
dbus_interface('jp.itline.test.Kazuate.master') { # Interface
# 数の範囲の変更メソッド
dbus_method(:range, 'in range:i, out res:s') {|range| # Member
@@range = range
'Range is set.'
}
# 答えを見るメソッド
dbus_method(:answer, 'out ans:i') { # Member
@@answer || -1
}
}
end
# DBus のセットアップ
bus = DBus::SessionBus.instance # Bus is session (not system)
bus.request_name('jp.itline.test.Kazuate') # Service (for --dest)
bus.object_server.export(DBusKazuate.new('/jp/itline/test/Kazuate')) # Object path
DBus::Main.new.tap {|main|
main << bus
}.run
$ dbus-send --session --dest=jp.itline.test.Kazuate --print-reply --type=method_call \
/jp/itline/test/Kazuate \
jp.itline.test.Kazuate.ready
string "I'm thinking of a number between 1 and 10."
$ dbus-send --session --dest=jp.itline.test.Kazuate --print-reply --type=method_call \
/jp/itline/test/Kazuate \
jp.itline.test.Kazuate.try int32:5
string "Too low!"
$ dbus-send --session --dest=jp.itline.test.Kazuate --print-reply --type=method_call \
/jp/itline/test/Kazuate \
jp.itline.test.Kazuate.master.answer
int32 8
$ dbus-send --session --dest=jp.itline.test.Kazuate --print-reply --type=method_call \
/jp/itline/test/Kazuate \
jp.itline.test.Kazuate.try int32:8
string "Congratulations!"
$ dbus-send --session --dest=jp.itline.test.Kazuate --print-reply --type=method_call \
/jp/itline/test/Kazuate \
org.freedesktop.DBus.Introspectable.Introspect
:
<interface name="jp.itline.test.Kazuate">
<method name="ready">
<arg name="res" direction="out" type="s"/>
</method>
<method name="try">
<arg name="guess" direction="in" type="i"/>
<arg name="hint" direction="out" type="s"/>
</method>
</interface>
<interface name="jp.itline.test.Kazuate.master">
<method name="range">
<arg name="range" direction="in" type="i"/>
<arg name="res" direction="out" type="s"/>
</method>
<method name="answer">
<arg name="ans" direction="out" type="i"/>
</method>
</interface>
</node>"
2025-01-27(Mon) シン・DBusで数当てゲーム
「続いて、クライアントや通信キャプチャを試してみたい」というわけで、クライアントを書いてみた。「数当てゲーム」クライアントだ。
#!/usr/bin/env ruby
require 'bundler/setup'
require 'dbus' # bundle add ruby-dbus
bus = DBus::SessionBus.instance
service = bus['jp.itline.test.Kazuate']
object = service['/jp/itline/test/Kazuate']
interface = object['jp.itline.test.Kazuate']
interface_m = object['jp.itline.test.Kazuate.master']
puts(interface_m.range(100))
puts(interface.ready)
loop {
print("\nguess> "); guess = $stdin.gets.to_i
puts(res = interface.try(guess))
res =~ /congra/i and break
}
$ ./kazuate_cli.rb
Range is set.
I'm thinking of a number between 1 and 100.
guess> 50
Too low!
guess> 80
Too low!
guess> 90
Too high!
guess> 85
Congratulations!
Linuxサーバにおける大概のトラブルはtcpdumpによる通信キャプチャで切り分けられるので、DBusに対する通信キャプチャも重要だ……と思ってたのが、基本的な使い方がわかったあたりで、当初の問題はDBusと関係なさそうだという雰囲気が見えてきてしまった。
$ dbus-monitor --session
$ dbus-monitor --session --profile
2025-01-31(Fri) 業務チャットの中からツンデレAIに話を振ってみる
2020年の頭。新型コロナの流行が始まって約1年。テレワークが定着しはしたものの、マイクソソフトのTeamsでは、サシの会話しかできず、周囲からの助言を受けられない、とボヤく後輩に、そんなんメールでエエやん、と答えたところ、そんな面倒なのやってられませんよ、そうかぁ? そうですよ、いまやチャットですよ。うーん、じゃ、作る? そうだよなぁ……それしかないなぁ……という感じでほぼスクラッチから作り始めた。
その会話が2021年2月3日。リリースが8日。エラく早いが、夢中になって作ってたし、その時点で使い物になるレベルには持っていってたはず。そこから機能追加はしたが、ほぼ停止することなく、これまで運用し続けている。途中で人狼ゲーム機能とかも付けたりもしつつ。我ながら、ちょっとした偉業だと思う。
イマドキフツーすぎるやろ……と思うかもしれないが、なかなかどうして、チャット中から尋ねられるのは相当に便利である。それまでの会話の流れを入力する必要がないからだ。AIに話題を振るには、文末に「>リナ」と入力するだけ。
# ルーム更新時のフック、特定のキーワードの書き込みに対して、AI に問い合わせ、結果を書き込む
@configs[:post_ai_comment] = {}
@configs[:post_ai_comment][:ws_uri] = 'ws://127.0.0.1:33109/'
@configs[:post_ai_comment][:user] = 'リナ'
@configs[:post_ai_comment][:ucrt] = '0123abcd'
@configs[:post_ai_comment][:keyword_re] = '(.+)>リナ\s*$'
@configs[:post_ai_comment][:contexts_max] = 10
@configs[:post_ai_comment][:ex_users] = { 'Mezatalk' => true }
@configs[:post_ai_comment][:wrap] = 78
@configs[:post_ai_comment][:tones] = ['ツンデレ女子の口調で'] # + [''] * 10
#configs[:post_ai_comment][:prompts] = {}
#configs[:post_ai_comment][:prompts][:def] = 'あなたは「リナ」という名前で、Linux/OSSのエキスパートです。'
#configs[:post_ai_comment][:prompts][:say] = '「%s」さんが%sと発言しました。'
#configs[:post_ai_comment][:prompts][:ask] = '「%s」さんから%sと問いかけられました。'
#configs[:post_ai_comment][:prompts][:com] = 'それに対しての助言を%sお願いします。'
@configs[:post_paragraph_hook] = Proc.new {|room|
IO.popen(['./post_ai_comment', room], $stderr => open('/dev/null', 'w')) {|io|
io.read
}
}