SVX日記

2004|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|
2021|01|02|03|04|05|06|07|08|09|10|11|12|
2022|01|02|03|04|05|06|07|08|09|10|11|12|
2023|01|02|03|04|05|06|07|08|09|10|11|12|
2024|01|02|03|04|05|06|07|08|09|10|11|12|
2025|01|02|03|04|05|06|

2004-12-28(Tue) コロがしたい、マキこみたい

  先日かきかけのまま放置してある塊魂のレビューだが、なにしろこのゲームは「人間の爽快感を刺激してくるゲーム」であるとまとめたい。少なからずゲームにはそういう面がある。シューティングの破壊衝動、インベーダやパックマンの掃除感覚、テトリスの整理整頓。しかし、このゲームの面白いトコロは敵を「制限時間」のみに絞って徹底的にゲーム性を排除し、他のゲームにはない爽快感を持たせている点であろう。つまり「人間の爽快感を刺激してくる『だけの』ゲーム」ということだ。

  結果、この類のゲームの最大の勲章であると私が考える「普段の生活へのオーバーラップ」現象を勝ち得ている。街を歩いているだけで「転がしたく」なって「巻き込みたく」なってくるのである。今まで私からこの「普段の生活へのオーバーラップ」現象を勝ち得たゲームは「テトリス」ぐらいか。ビルの窓など格子状のモノを見たりすると頭の中にブロックが降り始める病気には、かなり長い期間に及んで悩まされたものである。

  そして今は「塊魂」だ。そして私的にかなり転がしたい場所、それはズバリ「秋葉」である。あれだけゴチャゴチャした場所を転がしたらさぞ爽快であろう。駅前から路上の吸殻等を巻き込みつつラジオセンターへ。細かいチップ等をワシワシと巻き込みつつ抜け、信号を渡ってラジオデパートの中へ。この頃には大きめのトランスまで巻き込める大きさに成長しているハズだ。向かいのオモチャ屋もひと転がししておくか。あとは順当にノートPC、デスクトップPC、ディスプレイの順で巻き込む大きさを増していったら、次のターゲットはヲタク達。浜田電気から、ヒロセテクニカル、秋月、鈴商と抜けながらオタクを一網打尽にすれば、路肩のバイクを巻き込めるサイズに成長しているはず。あとは道路上のクルマを巻き込みつつ、右回りで山手線方面に向かい、小さな店舗を巻き込みつつ、トライアミューズメントタワーを巻き込めないまでもひと揺らし。目抜き通りに戻って小さな店舗を一掃してから、ツクモの黒いビル、秋葉原駅を巻き込んで、タイムアップである。やっふー。

  と、ここで思った。このゲームをトラックボール対応にしてはどうだろうか? 転がすスピードを変化できれば、それを活かしたゲーム性を追加することもできる(このゲームの場合ゲーム性を低く保つことで一般受けを狙っている面があることもわかっているが、テクニカルな要素を追加することでゲーム好きを満足させる方向性に進むのも間違いではないだろう)。後楽園遊園地を転がして勢いをつけてジェットコースターのコースからマーブルマッドネスのようにジャンプ!! ところが、落ちたところはK1を興行中の埼玉スーパーアリーナ!! それじゃマーブルマッチョネスやがなッ!? 国技館に落ちればマーブルファットネスかいッ!! ……お後がよろしいようで。

  個人的には秋月をひと転がしするだけで満足なんですがね(^-^;)。


2008-12-28(Sun) フルタニアン宮殿を復興

  サーバ移行の手始めに、その昔に建造したフルタニアン宮殿を復興してみた。

  画像の説明

  手前味噌だが、おもしれぇ。懐かしさもあって、自分で見入ってしまって、作業が進まねぇったらない。


2013-12-28(Sat) 艦これ、E-3突破

  結局、イベントが始まったらガリガリと取り組んでしまい、気づくと海域突破。前回のイベントと違い、それほどシビアでないのがうれしい。アルペジオも楽しめているしなぁ。

  画像の説明

  とかやりつつ、工作にも取り組む。ブレッドボードから基板への載せ替え、完了。

  画像の説明

  どうも最近、ダイエットが成功し過ぎて、胃が小さくなってしまったようだ。昼に食ったうどんが多かったようで、気分が優れず、早寝……うぅむ。


2021-12-28(Tue) キミはそこにいるべきなんだ!

  先日、SK11の21mmのソケットを購入した。ロードスターのホイールナットを締める用である。SK11のソケットレンチセットは19mmまでなので、追加購入という形だ。

  しかし、ポツンとソケットだけあっても困る。ソケットのひとつくらい、レンチセットの箱に収まらないものか、と、思ったら、丁度いいスペースがあるではないか。カッターで丸く切り抜いて、収まるようにする。

  画像の説明

  まさに「僕はここにいてもいいんだ!」という感じである。おめでとう。ソケットくん。


2024-12-28(Sat) WebSocketクライアントを実装する

  しばらく前にMezatalkというチャットツールを作り、職場で活用している。チャットツールといえばWebSocketだ。発言の送信や受信には必須の機能である。通常、発言はブラウザのJavaScriptから行われる。が、Ruby版のコマンド「wsclient」も用意してある。ボットに発言させたい場合などに使える。こんな感じだ。

res = system('./wsclient',
	'ws://127.0.0.1:33109/',
	"{:REQUEST=>'login', :TYPE=>'talk', :USER=>'user1', :ROOM=>'_t~room1'}@@login",
	"{:REQUEST=>'sentence'}@@Hello.",
)

  実際、先日に記事にしたXalebotは、その名の通りボットであり、問い合わせに対する回答案などをMezatalkに発言するという連携機能も備わっている。

  で、今回「特定の発言が行われたら、別のシステムでその発言を処理する」という機能を実装する必要が生じた。まずは、別のシステムから発言を取得できるようなAPIを実装し、WebSocket経由で発言を取得できるようにした。さらに、発言が行われた場合に、設定ファイル中に記述した関数を呼び出す機能を実装し、そこから「wsclient」を実行しようとした、のだが……それが、どうやっても動かない。普通にコマンドとして実行すれば動くのだが、設定ファイル中からだと動かない。にっちもさっちもよっちもごっちも動かない。ドハマリ。

  「wsclient」は「em-websocket-client」というRubyのライブラリを使っているのだが、見よう見まねで書いたコードなので、それ以上に追求のしようがない。うーむ、こうなったら、独自に実装するか。

  というわけで、RFCの6455「The WebSocket Protocol」を眺めつつ、チマチマと実装していく。HTTPで接続後、プロトコルを切り替えたり、クライアントからの送信内容にはマスクを施したり、なんかいろいろと珍しい処理がある。面倒クサいが面白い。面白いが面倒クサい。

  そしてデキたのがコチラです。

#!/usr/bin/env ruby
 
require './wshelper'
require 'timeout'
require 'socket'
 
wsh = WebSocketHelper.new(uri = ARGV.shift)
 
Timeout.timeout(3) {
    sock = TCPSocket.open(wsh.uri.host, wsh.uri.port)
    sock.syswrite(wsh.handshake)
    sock.sysread(9999)
 
    while(request = ARGV.shift)
        sock.syswrite(wsh.encode(request))
        puts('[%s]' % wsh.decode(sock.sysread(9999)))
    end
    puts('Closed.')
}
require 'uri'
 
class WebSocketHelper
 
    attr_reader :uri
 
    def initialize(uri)
        @uri = URI.parse(uri)
    end
 
    def handshake
        (<<REQ % [@uri.path, @uri.host, @uri.port, make_websocket_key]).gsub(/\n/, "\r\n")
GET %s HTTP/1.1
Host: %s:%s
Upgrade: websocket
Connection: upgrade
Sec-WebSocket-Key: %s
Sec-WebSocket-Version: 13
 
REQ
    end
 
    def make_websocket_key
        nonce = []; 4.times { nonce << rand(0xFFFFFFFF) }
        @websocket_key = [nonce.pack('N*')].pack('m0')
    end
 
    def encode(req)
        make_masking_key
        head(req) + payload(req)
    end
 
    def make_masking_key
        @masking_key = rand(0xFFFFFFFF)
    end
 
    def head(req)
        head = ''
 
        fopc = 0
        fopc += (fin = 1) << 7
        fopc += (opcode = 1)
        head << [fopc].pack('C')
 
        mplen = 0
        mplen += (mask = 1) << 7
        if((it = req.length) < 126)
            mplen += it
            head << [mplen].pack('C')
        elsif(it < 65536)
            mplen += 126
            head << [mplen, it].pack('Cn')
        else
            mplen += 127
            head << [mplen, 0, it].pack('CNN')
        end
 
        if(mask == 1)
            head << [@masking_key].pack('N')
        end
 
        head
    end
 
    def payload(req0)
        len0 = req0.length; req = req0.dup
        req << "\x00" while(req.length % 4 != 0)
        res = []; req.unpack('N*').each {|u32|
            res << (u32 ^ @masking_key)
        }
        res.pack('N*')[0, len0]
    end
 
    def decode(res)
        fopc = res.slice!(0, 1)
        mplen = res.slice!(0, 1).unpack('C')[0]
        if(mplen < 126 and mplen == res.length)
        elsif(mplen == 126 and (res.slice!(0, 2).unpack('n')[0]) == res.length)
        elsif(mplen == 127 and (res.slice!(0, 8).unpack('NN')[1]) == res.length)
        else
            raise('Unexpected.')
        end
        res
    end
end

  まぁ、ヤルことちゃんとヤッてない。んが、仕事はキッチリこなします。まるで、オレみたいなヤツだな。んが、やっぱり、設定ファイル中から呼ぶと動かない。にっちもさっちもよっちもごっちも動かない。な、なんでぇ?

  終いにはtcpdumpでパケットまで確認してしまう。要求は出ている。んが、応答が返らない。設定ファイル中から呼んだ場合だけ。なんだこれ。いや、正確にはタイムアウトした瞬間に応答が返る。なんだこれ。なんだこれ。なんだこれ。サーバ側の問題?

  サーバ側は「em-websocket」というRubyのライブラリを使っているのだが、見よう見まねで書いたコードなので、それ以上に追求のしようがない。うーむ、こうなったら、独自に実装するか……って、イヤ、それはオオゴトすぎるべ。さすがに、これまで4年近くも動いているコアの部分はイジるべきではないだろう。

  そこでようやく気がついた。サーバ側に追加した、このコード。

it = @configs[:post_paragraph_hook] and it.call(room)

  これ、connection.onmessageの延長、つまり、コールバック関数の中で動いている。そんなトコで、さらに「wsclient」で要求を出して応答を待ったって、サーバ側もそこで待っとるっちゅーねん。つまり、要求を処理するヤツが、要求を出して応答を待ってたって、応答するヤツはテメエ自身なんだから、応答が返るわけがない。ぷふゎぁ〜……。

  長らくプログラミングしているが、こんな状況は初めてだなぁ。じゃ、どうすりゃいいか。こうすりゃいいだけだ。

it = @configs[:post_paragraph_hook] and Thread.new {
    it.call(room)
}

  ちゅーわけで、動くようになった。結局、元の「wsclient」が使えたので、WebSocketクライアントの独自実装は徒労に終わった。でも、結果として目的は達成できた。結果オーライ。

  けどね。エンジニアの諸君には言うまでもないだろうが、技術力というのは膨大な徒労によって培われるものなのだ。見える成果だけを成果とするならば、それは当たり馬券以外には金を払わない、と主張するのと同じ。

  しかし、久々に悩ませられたなぁ。そのぶん、動いたときの嬉しさは大きかった。これだからプログラミングは、やめられまへんなぁ。