SVX日記
2005-05-01(Sun) 古典名作ゲームの保存について考える
ココは以前から魅力を感じていた場所である。そこで今日はデジカメを持っているし、適当に掘り出し物を漁りつつコッソリ写真を撮ってしまうコトにする。特にココの二階は九龍城かと思うほど、カオスな感じがステキなのである。
ガサガサと箱の中を漁っていたらドラクエ2のカートリッジが見つかった。ファミコン用のオリジナルである。おそらく90円で売ってくれるモノとは思うが、ファミコン本体がないとどーしよーもない。できれば、ゲームボーイ用のドラクエが欲しかったのだが。
そして二階。もぅ、狙っているとしか思えないようなマネキンの配置である。廃墟好きなヒトには堪らない光景だ。しかしこれはヒトケタ国道沿いの店なのである。イイ。すっげぇイイ。すっげぇイイが特に購入したいアイテムは見つからなかった。さようなら。
その後、3軒ほど古本屋系の中古ゲームショップを回るが、ドラクエは見つからない。ドラクエは1と2をひっつけたモノがゲームボーイカラー用に出ているらしいトコロまでは調べたのだが、売っていない。ついでに調べたところい、ゲームボーイの系譜は、元祖ゲームボーイ、ゲームボーイカラー、ゲームボーイアドバンス、ニンテンドーDSという進化を遂げており、すでに元祖とカラーはディスコンという扱いらしい。中古屋をみても、ゲームボーイカラー用のソフトは千円以下がメインで裸で転がしてある。
しかしオイラは単にドラクエをやってみたいのだからして、別に現行の機械を買おうとはサッパリ思わない。むしろゲームボーイカラーは現行から外れているために、妙に安いのが魅力なのだ。千円前後で売っている。これでソフトが2千円以下程度であれば、3千円でドラクエが楽しめるのだが。
と、ココでふと矛盾に気がついた。家庭用ゲーム機というジャンルが確立して20年、そろそろソフトとハードを分離してもいい頃じゃないか、と思うのだ。遊び捨てるようなソフトはともかく、ドラクエや、ナムコのオールドゲームなんかは、小説でいえば「古典文学」として扱ってもよい後世に残すべき傑作だと思うのだ。
事実、ナムコのオールドゲームはあらゆるプラットホームに移植されている。ココに矛盾を感じて欲しいのだ。何度も何度もプラットホームが変わるたびに再プログラムせねばならない。そしてその都度、マニアに移植度がどうとか突っつかれるのである。
それに対する、ひとつの回答はエミュレータである。元来のコードを利用するのだから、移植度が低いわけがない。しかし法律的にグレーなのが痛いし、所詮は有限である各ハードウェア(主にIBMPC)に依存するのが面白くないトコロである。オイラが欲しいのは、ゲームの古典文学を正々堂々、永久に所有し味わうコトのできる権利なのである。
そして提案するのだ。ハードの性能をギリギリの引き出したソフトはそれとして、もう少しランクの落ちる共通のプラットホームを立ち上げようではないか。この考え方は、その昔Oh!mzという雑誌で提案されたCIOSの考え方とほぼ同じである。CPUがZ80という共通のプラットホームの下に、最低限の共通ルーチンを持たせ、その中では共通のアプリケーションが動くようにするというシステムだ。
そしてこのゲーム版が「Common-Game-Playing-System」略して「CGPS」だ。CGPSは特定のハードに依存せず、規格として提唱されるコトで、永遠に継続が可能なプラットホームとなるのだ。規格はオープンであるため、規格に準拠すれば特にロイヤリティ不要でハードもソフトも作れるものとしてしまおう。最初の段階ではCGPS-ver.1.0として、拡大縮小可能なスプライトとFM音源相当あたりを必須性能に設定しようか。現実のゲームプラットホームに例えるなら、ナムコのシステム2基板相当というトコロだ。
望ましい展開としては、ニンテンドーDSとPSPにどーにかしてCGPS-ver.1.0をサポートしてもらいつつ、シャープがザウルスの付加機能として、DoCoMoとAUが携帯電話の付加機能としてサポートするというトコロだろうか。同時にキラーソフトを出すことも忘れてはならない。ナムコからはナムコエターナルシリーズとして1,2,3を同時発売しよう。1,2はいわゆる定番を5本ずつまとめつつ、3には「アサルト」「ワルキューレの伝説」あたりを含めよう。
セガからは「体感ライブラリ」として「スペースハリアー」「アウトラン」「アフターバーナ」「パワードリフト」「ギャラクシーフォース」の5本組みを出そう。コナミからは「グラディウスレジェンズ」で全部入りだ。スクエアエニックスからは「ドラクエトリオ」と「ファイナルファンタジートリオ」の発売だ。カプコンなんかも「ストリートファイターズ」で全部入り。データーイーストも「デコシューチョイセズ」と「デコジャンプチョイセズ」を出してしまおう。他にも「オールタイプアールタイプス」「コンプリートダライアセズ」など……うぉぉ!! 買うぜ!! 全部買うぜッ!!
2008-05-01(Thu) 洗うThinkPad
連休中、名古屋の実家に帰っていた。ガキを新幹線に乗せるので、車中においてオイラのラップトップ(膝の上)は占拠状態になることは想像に難くなく、事実そのとおりになった。PCを持って帰らなくてよかった。ちなみに、行きは500系、帰りはN700だったが、N700の乗り心地の快適さは異次元であった。うぅむ、500系好きにはちょっと悔しいぞ。足元に妖しく光るコンセントもあったしなぁ。
連休前にTninkPad用のHDDを探しまくったところ、秋葉の若松で売っているのを発見した。んが、20GBで1万円……奥さんッ!! 20GBで1万円ッ!! ……うげぇ、である。80GBなら……いや、せめて40GBなら……いくらなんでも、20GBではちょっとバカバカしくてやってられん……そだろ?
一方で、そんな若松にはCFアダプタも売っている。コンパクトフラッシュを挿すと、1.8インチのHDDスロットにHDDとして挿せるというシロモノだ。しかし、最近は安くなったとはいえ、許せる容量のCFが、許せる値段で売っているもんかいな……と、調べたところ16GBで8千円弱からある。奇しくも、2千円弱のCFアダプタと併せると、ちょうど1万円である。
相手がHDDとなると圧倒的に高価だが、相手が「1.8インチのHDD」となると、検討対象になってしまうというのが、これまた絶妙すぎる巡り合わせといえよう。容量だけ比べれば4GB足りないが、ケタ違いのランダムアクセス性能と、ショック耐性、バッテリ容量への貢献を考えると悪くない。悪くないが、書き換え回数が有限というのは、気分がよろしくないのも確か……
……などと、結局、連休中にジワジワと考えつつも、結局、帰りがけに秋葉に寄って、両者を買い揃えてしまった。1980円+7800円。そんなにハッキリと意識したワケではないが、決め手は昨今のフラッシュメディアの値下がりの速度だと思う。SSDが普及域に入ってきた現在、下手をすると、1年後に32GBメディアが1万円弱に降りてきていても不思議ではない。そうなれば、その時点で追加購入、両者を併用すれば48GB。決して贅沢ではないものの、不満を感じるレベルからは脱せよう。一方で、HDDは併用できないから、先はない。
[root@raven ~]# fdisk -l /dev/sdb
Disk /dev/sdb: 16.2 GB, 16240345088 bytes
255 heads, 63 sectors/track, 1974 cylinders
Units = シリンダ数 of 16065 * 512 = 8225280 bytes
Disk identifier: 0x00000000
デバイス Boot Start End Blocks Id System
/dev/sdb1 * 1 1975 15859208 c W95 FAT32 (LBA)
[root@raven ~]#
2011-05-01(Sun) 布製ハチロク
で、買ったばかりのプリンタで印刷。型紙を作って、フェルトを切り抜く。
しかし、幼稚園で頭文字Dにハマってるのは、ウチのガキだけだと思うのだが、これでよかったのだろうか……。
2017-05-01(Mon) お風呂BluetoothスピーカV2とリチウムポリマ充電池対応
お風呂スピーカの稼働率が高くなるにつれ、電池の交換が面倒くさくなってきた。そんな時は充電池……なのだが、イマドキならば、リチウムポリマ充電池なわけで、でも、それはひとつ間違うと爆弾なわけで。
そんな気分でAitendoを見ると、ひと通り揃っているんですな、これが。リチウムポリマ電池(3.7V/110mAh)[SX-3.7V-110MAH]と、充電用IC[MCP73831T-2ACI]と、ピッチ変換基板[SOT23-B]。ついでにBluetoothオーディオレシーバ基板[BK3254P9]まで買ってしまう。
Bluetoothスピーカ化は既に済ませているのだが、USBメモリ形状のものを無理にくっつけているのがイマイチ。たまたま既存の極小MP3モジュール[M2801002]とピンアサインが似ているので、似たような形状に合わせることにした。同じようにチョンチョンと空中配線で2.54mmのピンヘッダにハンダ付けしてモジュール化する。
……が、なんだこれ。電源入れると中国語らしきボイスが流れるんですが……ペアリングにちょっと時間がかかるものの、特に何の操作をしなくてもペアリングされるのはいいが、その時にも中国語らしきボイスが流れる。まぁ、使用には問題がないからいいんだが、ちょっと微妙だ。
さて、本題はリチウムポリマの方。米粒のようなICをピッチ変換基板に載せる。手配してから気づいたが、充電電流によっては結構発熱するので、できるだけ放熱しやすいピッチ変換基板をチョイスしたほうがいいみたいだ。今回の充電対象は110mAhの容量なので、1Cで100mA程度の電流量であり、たぶん問題なさそうだが。
ハンダをドバっと乗せてから、はんだ吸い取り線で吸い取る形でハンダ付け完了。ブレッドボード上に周辺回路を組む。たまたま、USBのmicroBの変換基板を買ってあったので、それを経由して充電することに。PCのディスプレイ横のUSB端子から、USB電流モニタを介して、ドキドキしながら充電を開始!
時刻 | 電池電圧 | 充電電流 | 累計充電量 | |
---|---|---|---|---|
14:20 | 3.90V | 0.11A | 0mAh | |
14:32 | 3.95V | 0.09A | 24mAh | |
14:43 | 4.00V | 0.08A | 40mAh | |
14:52 | 4.04V | 0.08A | 53mAh | |
15:01 | 4.08V | 0.07A | 66mAh | |
15:10 | 4.13V | 0.07A | 76mAh | |
15:19 | 4.19V | 0.07A | 88mAh | |
15:23 | 4.19V | 0.00A | 92mAh | 電流値が0に(測定器の問題) |
15:30 | 4.19V | 0.00A | 96mAh | |
15:43 | 4.19V | 0.00A | 96mAh | |
15:48 | 4.17V | 0.00A | 96mAh | LEDが消灯 |
2023-05-01(Mon) つまらないFactorio
「一番最初のサラ地から遊びたいが製品版を買いたくないなぁ」ということで、5番目の体験版シナリオの既設の施設を全部回収し、サラ地状態にしてから始めることにした。意外と大した手間ではなかった。
2025-05-01(Thu) デスクトップでF1のラップタイム計測ごっこ
ラップタイムの計測をしたいなぁ、ということで、実装してみることにした。
計測には、いわゆる「当たり判定」が必要になる。以前に、2Dシューティングにおける「箱同士」の当たり判定は書いたことがあるのだが、同じ2Dでも任意の角度に回転する物体同士の当たり判定には別の方法が必要になる。
確かベクトル演算の手法が使えたような。ちょっと前に読んだ線形代数の本を引っ張り出してきて調べる。そうだった、外積だ。座標pが、座標a, bを通る直線のどちら側に位置するのかを判定できる。今回は、コース上のラップ計測ラインの通過を判定したいだけだから、それ一発で済む。まずは、理解するためのサンプルコードを書く。
#!/usr/bin/env ruby
# 座標 p が、座標 a, b を通る直線のどちら側に位置するかを調べる
def vec(i, j) # 座標 i->j をベクトル化
{ :x => j[:x] - i[:x],
:y => j[:y] - i[:y] }
end
def vcross(i, j) # ベクトル i, j の外積を求める
i[:x] * j[:y] - i[:y] * j[:x]
end
a = { :x => 5, :y => 3 } # 座標 a
b = { :x => 15, :y => 10 } # 座標 b
ab = vec(a, b) # 直線ベクトル a->b
20.times {|y|
20.times {|x|
p = { :x => x, :y => y } # 座標 p
ap = vec(a, p)
print(vcross(ab, ap) < 0 ? ' -' : ' +')
}
puts
}
+ - - - - - - - - - - - - - - - - - - -
+ + + - - - - - - - - - - - - - - - - -
+ + + + - - - - - - - - - - - - - - - -
+ + + + + a - - - - - - - - - - - - - -
+ + + + + + + - - - - - - - - - - - - -
+ + + + + + + + - - - - - - - - - - - -
+ + + + + + + + + + - - - - - - - - - -
+ + + + + + + + + + + - - - - - - - - -
+ + + + + + + + + + + + + - - - - - - -
+ + + + + + + + + + + + + + - - - - - -
+ + + + + + + + + + + + + + + b - - - -
+ + + + + + + + + + + + + + + + + - - -
+ + + + + + + + + + + + + + + + + + - -
+ + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
判定処理は毎フレームしこたま繰り返すので、計算を軽くしつつ、オブジェクト化して書き直す。a→bベクトルは一度計算するだけでいいので、TimingLineクラスの初期化時に行うようにし、判定をover?メソッドにまとめる。より、直感的な記述になりつつ、同じ実行結果が得られる。
#!/usr/bin/env ruby
# 座標 p が、座標 a, b を通る直線のどちら側に位置するかを調べる
class TimingLine
def initialize(a, b)
@a = a
@ab = { :x => b[:x] - a[:x],
:y => b[:y] - a[:y] }
end
def over?(p)
@ab[:x] * (p[:y] - @a[:y]) - @ab[:y] * (p[:x] - @a[:x]) < 0
end
end
a = { :x => 5, :y => 3 } # 座標 a
b = { :x => 15, :y => 10 } # 座標 b
tline = TimingLine.new(a, b)
20.times {|y|
20.times {|x|
p = { :x => x, :y => y } # 座標 p
print(tline.over?(p) ? ' -' : ' +')
}
puts
}
思索しつつ試作したコードを、CoffeeScriptで書かれたゲームのコードに落とし込む。それなりにオブジェクト化してあるので、どのオブジェクトに、どう落とし込むか、非常に考えどころである。とりあえず、地球上の位置(Wpos)クラス、コース(Course)クラス、メインプログラムに落とし込んでみた。
$ diff ../topdrivin.org/wpos.bean wpos.bean
85a86,92
> to_vec: (wpos) -> # 終点座標を渡し、ベクトル情報を生成
> @vec_x = wpos.wpx - @wpx
> @vec_y = wpos.wpy - @wpy
>
> vec_over: (wpos) -> # 座標を渡し、ベクトル線を超えたか返す
> @vec_x * (wpos.wpy - @wpy) - @vec_y * (wpos.wpx - @wpx) < 0
>
$ diff ../topdrivin.org/course.bean course.bean
19c19
< constructor: (_s, x, y, car) ->
---
> constructor: (_s, x, y, car, sectors) ->
21a22
> @sectors = sectors
54a56,58
> @sector = 0 # 開始セクタ(ラップタイム計測)
> @dir = _s['DIRECTOR']
>
80a85,91
> # ラップタイム描画
> @context.font = '24px sans-serif'
> @context.fillStyle = 'white'
> @context.textAlign = 'right'
> @context.fillText(@sectors[@sector]['name'], 100, 24)
> @context.fillText(@dir.tsc1000, 100, 48)
>
95a107,108
> if(@sectors[@sector]['vec'].vec_over(@car.wpos)) # セクタ計測ラインを超えた?
> @sector = (@sector + 1) % @sectors.length
$ diff ../topdrivin.org/sample_course_view.bean sample_course_view.bean
80a81,88
> sectors = [
> { l: [25.957223, -80.244210], r: [25.957362, -80.244212], name: 'dummy 05' },
> { l: [25.956204, -80.243633], r: [25.956089, -80.243657], name: 'sector 1' },
> { l: [25.958981, -80.229886], r: [25.958922, -80.229795], name: 'dummy 15' },
> { l: [25.960090, -80.230708], r: [25.960203, -80.230716], name: 'sector 2' },
> { l: [25.960523, -80.242873], r: [25.960578, -80.243020], name: 'dummy 25' },
> { l: [25.959922, -80.238718], r: [25.959788, -80.238811], name: 'finish line' },
> ]
83a92,96
> @tsc1000 = 0; @tsc1000inc = [17, 16, 17] # 1/1000時計を初期化
> for sector in sectors
> sector['vec'] = Wpos.deg(sector['l'][1], sector['l'][0])
> sector['vec'].to_vec(Wpos.deg(sector['r'][1], sector['r'][0]))
>
87c100
< @objs['COURSE'].push(new Course(@_s, 0, 0, mycar))
---
> @objs['COURSE'].push(new Course(@_s, 0, 0, mycar, sectors))
95a109
> @tsc1000 += @tsc1000inc[tsc % 3] # 1/1000時計を加算
マイアミサーキットの場合、各セクタの計測ラインが、いずれもUターンのようなコーナーの先にあるため、計測ラインの手前にダミーの計測ラインを設けている。そうしないと、各セクタの計測ラインを超える前に、超えたという判定になってしまうためである。いったんアッチに行ってからね、って感じ。
ちなみに、計測に使うタイムは、ゲームの固定FPS(1/60タイマ)に同期する仕様とした。ただし、1/60秒は割り切れない値なので、1000分の17, 16, 17を順に加算することで作り出している。これは逆に言うと、60FPSのゲームなので16/1000秒以下の計測粒度はない、ということなのだが、サウジアラビアの予選でポールのフェルスタッペン、ピアストリのタイム差は10/1000秒。それは、優に格ゲーの1フレーム以下の戦いだったってことである。マジかよ……。
それにしても、今回、最終的に追加したコードは意外なほど少ない。実は丸2日かかっているのだけれど。というのも、上述したように「どのオブジェクトに、どう落とし込むか」が非常に考えどころであり、楽しいところでもあるのである。「ラップタイム計測」をするのは誰(どのオブジェクト)であるべきか? 自車(MyCar)クラスか? 今回はコース(Course)クラスに追加したが、それで正しいのだろうか。今回は、ラップタイムの描画もコース(Course)クラスにやらせているが、ラップタイムの管理も含めて、ラップタイムクラスを新設するべきだし、それを駆動するのは自車(MyCar)クラスであるべきのようにも思える。
前にも書いたが、やっぱりプログラミングは盆栽だなぁ、と思う。処理をどこに足すべきか。それは、どの枝を伸ばすか、みたいなものなのではないか。間違ったら剪定して、また違う枝を伸ばしてみたり。