「プログラミング」に関する日記(28)

プログラミング関係の話題。プログラミングコーナーを更新したとか、プログラミングの内容に踏み込んだ話とか。

<前 次>

時間の問題

NeoMuplに新たな問題ですよ。
まずソートプログラムがO(n2)の処理時間なので数が多くなるとソートが圧倒的に遅くなること。
予想はしていたんですが項目が数千個になるとかなり時間がかかりますよ。
そしてもう一つ、こっちのほうが深刻です。
リストのロードとセーブに時間がかかるんです。
計算したらやはりO(n2)の処理時間。
項目のソートに関してはクイックソートを使うことで、ロードとセーブについては読み込んだ結果の参照が遅いということがわかっているので読み込んだ時点で読み込み結果をソートして、ソートされていることを前提とした検索で目的のデータを探すことにすれば、いずれもO(nlogn)の処理時間に抑えることができそうです。

一気に進む

はい、もう一気にできそうですね。
ループ処理も演奏位置を変更するだけで可能。
100ナノ秒精度ですよ。
ループ演奏・演奏後の処理など、基本的な処理が早くもできました。
あとはBGMモードを付け加えれば基本機能が出揃うわけです。
そうそう、データ記録周りを少し整理しました。
レジストリに記録する場合とINIファイルに記録する場合で同じものを記録する処理を2回書いていたのですがここを統一して書くのが一回で済むようにしました。
そして、忘れてませんよ。
NeoMuplは進んだら進んだだけたとえ中途半端でもアップします。

そういえば弟がメトロイドプライム2を買ってきていました。
グラフィックについては1がすごすぎたから別に驚かなかったんですがこれもやっぱりバンジョーと同じくやりすぎなほどの正当進化の予感がひしひしと伝わってきています。
そういえば最近爆ボン以外のゲーム記の更新が滞ってるな。
ま、いいけど。

DirectShow

MusicPlayer3の怪しい挙動にどうにも我慢ならん!
と、NeoMuplの開発のほうに行こうとしたのですが、現在のVC++6での開発状況が、MFCに挫折して自分でWindowsライブラリを作ると意気込んで作っていて色々面倒くさそうな作業がたまっていたことに気付き、C#にあっさり移行したのはよいのですが、手抜きのため導入したYanesdk.NETではMP3が鳴らせないしMIDIは左側からしか聞こえてこないしで今回の用途には使い物にならず、だったら自前で作るかと思ったらDirectX9には日本語ドキュメントがないしDirectMusicへの対応が怪しかったりで結局どっちもうまくいきそうにありませんでした。
そういえばDirectShowのヘルプに「Microsoft Visual Basic でDirectShow API のかなりのサブセットにアクセスできるが、Visual Basic のサポートは完全ではない。」って書いてあったけど、これはほとんど使えますと解釈してよいのかな…?
去年の12月にVBはいったん捨てたんですが実は再生用DLLを作る必要すらなく、VBオンリーでいけたようです。
最初からダメだと諦めて別の方法を探すって、うちの悪い癖だな。

VC++2005EEでWindowsプログラム

VC++.NETでVC6のインクルードファイルとライブラリを使ってWindowsアプリケーションを作るのに成功しました。
しかしやはりVC6とかなり勝手が違い、標準関数と自作関数以外はオートコレクトもないし、よく見たらVC6でも一応設定はできないけど最適化は自動でやってるみたいだし、やはりC++に関してはまだまだVC6メインでいったほうがよさそうです。

今日はMIFUMIDIAの次の更新のためいきなりアプレットの仕様が変更になっても対応できるようにCGIを手直ししていました。
どうにも今の仕様じゃ続きから作る機能が実装しにくいんですよ。

物理量解析プログラム

物理量解析プログラムができました。
二十キロメートルを20000mろ解釈したり2.65mとかも正しく解釈できたりします。
この解析には文字-意味対応をデータとして入力する必要があるのですが、さすがにオングストロームとか恒河沙とか天文単位とかはいらなかったような気がします。
年齢の表記も秒とか世紀とかも無意味にあったりします。
そのうち気が向いたら配布しようかな。

グランチルダがボールになる

弟がバンジョーとカズーイの大冒険をクリアしてました。
最後のグランチルダは強かったはずなんだけどチイトの体力自動回復がダメージを受ける勢いをはるかに上回っていたためほぼ体力全快で勝利。
グランチルダはかなり残酷な最期を遂げました。

物理量解析プログラムの解析には5つのレベルがあります。
レベル0:完全に確定した分
レベル1:確定した小数
レベル2:確定した整数
レベル3:千の位まで確定した整数
レベル4:未解析の連続した数値
レベルは基本的に解析が進めば下がるって最終的には0になります。
アルゴリズムが完成すれば公開してみようと思います。

Bomber丸WorldのBGMの変化パターンをいくつかに分類しようと考えています。
一つ一つにプログラムを組もうかとも思ったのですがデータと辻褄を合わせるのが結構大変そうだしめちゃくちゃ特別なことをするのではなく簡単なものを組み合わせるだけなので簡単なプログラムを組んでデータを渡すという方式にしたいのですよ。
サウンドテストを作るときにもプログラムとデータを分けたほうが都合がいいし。
フェード:指定の音量に徐々に近付く。目標値と時間を指定する。
プログラムチェンジ:音色を変更する。音色番号を指定。
即時切替:BGMを直ちに別の曲に変更する。BGM番号を指定する。
遷移:現在のBGMが終わった後に別の曲をかける。BGM番号を指定する。
効果音楽:指定の曲を現在のBGMに重ねる。ME番号を指定する。

物理量解析

物理量解析プログラム、1万km→10000000m みたいに変換するのを作ってます。
123万とか、十、二十、という数え方とか、小数点も考慮したり、とにかく書き方が無数にあるからプログラムにしづらいです。
まず、一億万とか、数として成り立たないものの解析結果は不定、つまりめちゃくちゃでかまわない。
そして都合上負の数は扱いません。
数値の解析については http://www.doumo.jp/postgretips/tips.jsp?tips=4 が参考になります。
数値を解析しつつ、単位が出たらその時点で確定です。

青と白

ブルーリゾートとホワイトアイス完成です。
今度はブラックシティ…アルタイルタワーはでかいから大変そうですが…。

そうそう、DirectX10SDKをダウンロードしました。
VC++6では引き続きDX8ですが.NETには.NET用のSDKがあるようなので。
そうそう、VC++6と.NETといえば、前の更新のときにわかったのですがVS2005EEって、最適化ができるんですね。
VC++2005EEは最適化ができるけどWindowsネイティブプログラムは作りにくい。
VC++6.0SSはWindowsネイティブプログラム作り放題だけど最適化ができない。
そしてVC++2005EEではスタティックリンクライブラリが作れます。
となれば、VC++2005EEでスタティックリンクライブラリを、目いっぱい速度優先にして作り、VC++6.0SSでそれを利用してソフトを作るのはどうかと思ったわけです。
プラットフォームSDKを使わなくて済んで、効率のよさが求められるアルゴリズムではVC++2005EEでライブラリを作って、メインのVC++6.0SSに組み込むんです。
次の更新は、VC++6.0SSで、直接コードを書いて最適化なしで実行するのと、最適化されたライブラリを呼び出して実行する場合の実行時間を比較する、ってのがいいかな。
で、早速ライブラリ一つ作ってみたんですが、もうびっくりするほど簡単でした。
ソースのCPPファイルを追加して一個関数書いてコンパイルしたらそれだけで一個の関数があるライブラリの完成。
難しそうだったら効率は度外視しようかと考えていたのですが、これはさすがに度外視するにできませんね。

サンプルのコンパイル結果

そういうことで中間コードを書いてみました。
とりあえず今のアルゴリズムとしては、計算式を見つけたら何はともあれ計算結果を一時変数に入れる、配列に対しては要素に直接値を代入するか要素を変数に代入するだけしかしない(変数名、インデックス、値で3つになるため)、命令を呼び出すときはパラメータをリストで渡して結果を一時変数で受ける、などなど、変換規則も考えつつ書いてみました。

# 例えば、イベントとか
msg("ハローワールド!\nこんにちは世界である!!");
&eee(select("それがどうした", "なるほど", "げこげこ"));

sub eee {
talk("みふみん", "あなたは".$_[0]."とおっしゃるのですね。");
if ($_[0] eq "げこげこ") {
for ($i=0; $i<10; $i++) {
$p = $i*($p+1);
msg($_[0].$p);
}
}
}

$param0[0] = "ハローワールド!\nこんにちは世界である!!";
$temp0 = msg @param0;
$param1[0] = "それがどうした";
$param1[1] = "なるほど";
$param1[2] = "げこげこ";
$temp1 = select @param1;
$param2[0] = $temp1;
@_ = @param2;
gosub eee;
$temp2 = $return;
goto LABEL0;
label eee;
$param2[0] = "みふみん";
$temp3 = "あなたは";
$temp4 = $_[0];
$temp5 = $temp3 . $temp4;
$temp6 = $temp5 . "とおっしゃるのですね。";
$param2[1] = $temp6;
$temp7 = talk @param2;
$temp8 = $_[0];
$temp9 = $temp8 eq "げこげこ";
if (!$temp9) goto LABEL1;
$i = 0;
label LABEL3;
$temp10 = $i < 10;
if (!$temp10) goto LABEL2;
$temp11 = $p + 1;
$temp12 = $i * $temp12;
$p = $temp12;
$temp13 = $_[0];
$temp14 = $temp13 . $p;
$param3[0] = $temp14;
$temp15 = msg @param3;
$i++;
goto LABEL3;
label LABEL2;
label LABEL1;
$return = undef;
return;
label LABEL0;

pseudoPerlコンパイラ

昨日の。
やっぱり中間言語レベルまではコンパイルしたほうがいいかな。
などと思ってます。
だって、逐次解釈とはいったって、式の中に命令が入っていたり、特定の命令で実行する場所が色々な場所に飛んだりして、今どこの何を実行中なのかがある程度プログラムからわからないとうまく動かないんですよ。
例えば2つの関数の結果を受け取って一つの価を返す関数。
f(g(x), h(y))
みたいなのね。
BASICのような逐次解釈じゃ難しいの。
最低限gとhの実行結果を持っておく変数が必要なわけ。
ああなんだかわからなくなってきた。
3番地コードで示される中間コードにコンパイルするの。
Add X Y Z
といったら
X = Y + Z;
と解釈するみたいに。
ん~。
ちょっとサンプルプログラムを手動コンパイルして雰囲気をつかんできますわ。

pseudoPerl

Bomber丸Worldのイベントや戦闘時のキャラクターの行動等はPerlに似たスクリプトで作ることにしました。
なぜPerlっぽいのにしたのか。
第一なぜ既存のプログラミング言語と似たようなものを使うことにしたのか。
完全オリジナルじゃいけなかったのか。
まずやりたいことを考えてみます。
1.計算式を使いたい。
2.イベントでも戦闘でも同じ感覚で記述したい。
3.決まりきった処理は関数のようにして呼び出したい。
4.変数や配列を使いたい。
5.変数は明示的に宣言しなくても使えるようにしたい。
6.数値も文字列も深く気にせず使いたい。
7.長い行は複数行に分けて書きたい。
8.変数と関数の区別を容易に付けたい。
9...きりがない。
もちろん、Perlを完全に実装するなんて無茶なことはしません。
例えば、便利そうでも実装が面倒な連想配列は実装しませんし、数値は解析の容易な整数のみです。
変数の省略も原則として認めませんし、標準関数はほぼ全て新しいものに置き換えられて、ローカル変数なども基本的にありません。
また、Perlは実行時にコンパイルして中間コードを生成し、高速化を図りますが、全体的なスピードが要求されず、多くのスクリプトが次々呼び出されるRPGではインタープリタ式のほうが適切だと思うので今回はコンパイラではなくインタープリタとして作ります。
一応言語仕様そのものは汎用性のあるものに作るつもりなので、出来上がったインタープリタは今後公開することがあるかもしれません。

値の重複を許さない整列された配列

System.Collections.Generic.SortedListって遅い!!
なんちゅう遅さだ!!
O(n)で要素を追加できるからx個のデータの中からn個をを追加するならO(xn)ですむはずなのにものすごく遅い!!
他のSortedDictionaryも同様。
私が欲しかったのは要素の追加が(logn)程度で行える、整列された、値の重複を許さない、値のコレクションだったのですが、どうにもなさそうだったので普通の配列で頑張ってみることにしました。

string[] outArray = new string[1];
int count = 0;
bool found;
foreach (string data in inArray)
{
found = false;
foreach (string item in outArray)
{
if (item == data)
{
found = true;
break;
}
}
if (!found)
{
if (count >= outArray.Length) Array.Resize(ref outArray, count * 2);
outArray[count] = data;
count++;
}
}
Array.Resize(ref outArray, count);
Array.Sort(outArray);
これ、O(n2)の処理なんですがもう感動的な早さです。
本当はゲームのキャラデータの一部から抜き出しているのでinArrayの要素をそのまま使っているのではなく、Sorted~にそのままデータを渡すことができないため一つ一つSorted~クラスにデータを渡していたせいで遅くなったものと考えています。
このアルゴリズムでは最初に値の重複を許さない要素の配列を作って後から整列しています。
最後のソートは元々高速なので実質配列の生成に最も時間がかかっています。
ちなみに、配列を延ばすのにも時間がかかるはずですが、配列の長さの初期値を増やしたところで大して高速化しなかったのでとりあえず最小の1にしています。

VB + Java = C#

VisualC#、どちらかというと―IDEの影響が大きいかもしれませんが―VisualC++6よりははるかにVisualBasic6に近い感じがします。
ちょっとコードの一部を出してみると、
pnlInfoUpper.Height = cmbName.Height * 3;
lblName.Left = lblGame.Left = lblInfo.Left = 0;
lblName.Height = lblGame.Height = lblInfo.Height = lblGame.Top = cmbGame.Top = cmbName.Height;
lblInfo.Top = cmbName.Height * 2;
lblGame.Width = cmbName.Left = cmbGame.Left = lblName.Width;
data.Load();
ChangeView(ViewMode.ABC);
LayoutInfo();
プロパティを指定してコントロールの大きさを変更してるあたり、いかにもVBです。
他にもVBと同じ仕様のforeachがあったり(本当にVBのFor Eachに括弧をつけただけです)、refをつけて明示的に参照渡しにしたり(VBではByRef)、VBをC言語風の見た目にしたような感じでした。
ファイルの読み書きについてはJavaに似ていて、System.IO名前空間のクラスを使って読み書きします。
StreamReaderはテキスト読み込みクラスで、
sr = new StreamReader(filename, Encoding.GetEncoding(932));
でテキストファイルを開き(Encoding.GetEncoding(932)はシフトJIS)、ReadLine()メソッドで一行ずつ読み込めます。
もしかしたら変な予備知識がない分VB.NETやC++.NETより使いやすいかもしれません。
いや、だからこそC#にしたんですけど。
そうそう、コーディング中にもガンガンエラーを出したりヒントを出したりしてくれるので下手にヘルプを引くよりまずは何かを入力してみるというのが結構有効です。
そして、ヘルプは、大して速くもない自分のパソコンを使うよりも、Googleで検索したほうが圧倒的に早く、正確な情報が得られます。

もうすぐミディビ完成させたい

う~む、C#のコントロールによるレイアウトはJavaのように自然な大きさに調整されるとかそういうのはないみたいです。
でもまあレイアウト関連はVBとかに比べるとやっぱり自動的にやってくれる部分が多くて助かります。
えっとですね。C#。どっちかというとIDEも含めればVBに近いです。
VBをC風の文法でかけるようなものです。
オンラインヘルプがあまり当てにならないのがつらいところですがメンバ自動表示や、ツールチップなどで機能を簡単に説明してくれたりするのでそっちで補えそうです。

そうそう、ミディビ更新しましたよ。
内容はバグ修正のみ。プログラミングコーナーも更新です。

C#

しかしVC++.NETを触ってみた感想、.NETにあわせてC++が結構拡張されてるみたいです。
^って何よ。
下手したらMFC以上にタチ悪いかも??
^というのはどうにもオブジェクトのハンドルらしいです。
gcnewで生成したオブジェクトを代入しているあたり、どうもガーベジコレクタが働いて、使われなくなったら自動的に解放されるらしいです。
しかし、後付けの機能が増えるに従い際限なく複雑化するC++がいい加減嫌になってきたので.NETはC#にすることにしました。
VC++2005はWindowsネイティブもできるから選んだというのに色々面倒なステップが必要な上にRADツールもつかずSDKは英語ということで、とても実用に堪えるものではなく、騙された気分でした(VC6があるので)。
というわけでVC#を導入。
なんかこれはこれでライセンス登録が必要らしいです。
でも少なくとも.NETに無理矢理C++を当てはめたVC++.NETよりは見通しがよさそうな気はします。

ドットネット

ドラクエ8のもう一つのエンディングを見てしまいました。
本当はただラスボスのマダンテが見たかっただけだったんですが、たいした技を使うひまもなくあっさりお亡くなりになられたのでそのままエンディングに入ったのですが、そこでアルゴンリングをもっていることに気づき、ククールにも指摘されたのでちょっと王様に見せてみたらなんか駆け落ちするはずだったところが堂々と結婚できることになり、チャゴスが前にも増して気の毒なことになってしまいました。

今日はMFCの練習もかねてキャラボックスのC++バージョンを作ろうとしたのですが、MFCをどう使っていいのか、画像形式は何が使えてどこまで表示できるのか、などがわからず断念し、.NETならどうかなと思ってVC++2005Expressをインストールしてみました。
ただ、インストールに時間がかかってしまったので本格的に使い始めるのはまだ先になりそうです。

ビットフィールドなんて知らなんだ

プログラミングコーナーのビット操作のところにビットフィールドのことを追記しました。
構造体やクラスの中限定で任意のビット長をもつ変数が使えるというのです。
ただ、ビットフィールドは構造体等の32ビット境界があるのでうまく変数をつめて書かないと帰って無駄が大きくなることもあり、また、配列が使えないという欠点もあったりします。

Bomber丸Worldでは、行動アルゴリズムをクラス化し、さらにGoToを実装しました。
GoToという行動タイプは今までになかったものですが、これは目的地をマップ座標で指定して、その場所へ向かうという行動を取ります。
簡単に言えば、人を追いかけるFollowの場所版ということです。
この行動は、まず目的地を最高点にし、他の場所に目的地に行くのにかかる歩数分だけ低くした点数をつけ、自分は最も点数の高くなる方向へ向かうというアルゴリズムになっています。
これ、まず全ての場所を最低点で初期化し、次に目的地を最高点にして、その場所を中心とした回り4箇所に点数をつけ、次は今点数をつけた周りに点数をつけ、更に次も同じようにして点数をつけ、自分の位置にたどり着くまでこれを繰り返します。
ただ、これを馬鹿正直に全部の場所を調べて点数をつけていたら検査する歩数の3乗のオーダーで処理回数が増え、高々32歩でたどり着く場所に1万回以上の処理が必要になったりするのでうまいこと処理を省くために調べる範囲を減らしたり調べるまでもないマスを読み飛ばしたりしています。

3Dの結末

どないしよー。
それでDirectDrawとDirect3Dでやってみようと思ったんだけど3D部分にドット絵を置いたらやっぱりドット抜けが起こって元の木阿弥なのです。
じゃあそもそも3Dでやりたかったことって何だったのか。
まずマップの前後関係のためのZバッファはZソートによって完全に無意味になっています。
エレコやギャモンは、正直、ポリゴンでさえあればなんだっていいし、アクセラレータを使わずとも自前の計算で充分何とかなるポリゴン数に収まるはずなのでこの点でもあまり意味はないわけです。
そしてDirectDrawだけではできないこと、半透明処理…やねさん、またライブラリ貸してもらいます。

バージョンダウン

えっとですね、描画エンジン、マップ表示の段階が出来上がりました。
結果はというと・・・・
・・・・
・・・・
惨憺たる結果でした。全くお話にもなりません。
ハードウェア的な相性もあるのでしょうが、これではドット絵など使えたものではありません。
とにかく今わかったことは、DirectX8はドット絵の描画にはとても使えたものじゃないということ。
おそらくDirectX9でも10でも同じことでしょう。
将来この問題が改善されるとしても私はもうそんなものに興味はありません。
逆にバージョンを下げてDirectX7を使えばDirectDrawというドット絵に特化したコンポーネントがあります。
そしてDirectDrawはDirect3Dの描画先として使うこともできます。
バージョンを落として不自由なことはありません。
プレイできる層が増え、更に表現力まで高まるのなら、それを使わない手はないでしょう。

ちなみに画像はDirect3D8で小さなドット絵を貼り付けたポリゴンを等倍でレンダリングした結果を拡大表示させたものです。
小さくレンダリングした時点でドットが潰れ、更に拡大表示する段階で画像が大きく歪んでしまっています。

1136812679.png

再利用性

もう言ったと思いますが、Bomber丸Worldのグラフィックは2Dに見せかけた3Dにしています。
そして今日作ったのはテクスチャ管理システム。
今回はRPGである都合上、画像をふんだんに使いますので、その画像を効率よく処理する仕組みが必要というわけです。
3Dで作るということで画像の表示はポリゴンにテクスチャを貼って表示させるわけですが、RPGのマップだと同じマップチップをいろんなところで表示したり、戦闘画面では同じ種類の敵が複数登場したりと、同じ画像が複数の場所で使われるケースが多いため、使う数だけ同じ画像のテクスチャを作っていては無駄であり、特にマップチップだと同じテクスチャ(それも結構でかいやつ)が何百枚と作られて、まともに動かせる状態ではなくなってしまいます。
だから、同じ画像のテクスチャを作ろうとしたときは前に作ったテクスチャを代わりに使うようにするわけですが、RPGでは場面の切り替わりと共にさっきまで沢山使われていた画像が今度は全く使われなくなるというケースもあり、逆にもう使われなくなった画像はどんどん破棄していく仕組みが必要なわけです。
ちなみにふにゃさんでは画像再利用をマップキャラ(ふにゃやギヤバネなど)に使っていたのですが、画像のサイズは高が知れているし、どのキャラもその後近いうちに再び使われる可能性が高いということで、読み込み時間を減らすため、破棄するのは絶対に使われなくなってから、つまり、ゲーム全体の終了時のみになっていたりします。
話を戻しますと、幸いにしてDirectXの全てのインターフェースはそのための参照カウントシステムを持っているため、ファイル名とテクスチャを関連付けてリスト(実際にはSTLのmap)に記憶しておき、あるファイルのテクスチャを使うときはリストからファイル名に対応するテクスチャを探し出し、見つかったらテクスチャの参照カウントを1増やしてからそのポインタを確保しておき、見つからなかったらテクスチャを作成した上でリストに追加、逆に使い終わったときは必ずリストにあるはずのテクスチャを探し出して参照カウントを1減らし、0になったらテクスチャを破棄(これは自動で行われる)した上で、リストからもテクスチャの情報を削除。
これで、常に必要な分のテクスチャだけがメモリ上に置いてあるということになります。
もしかしたらこういうことはDirectXが勝手にやってくれてて自分は無駄なことをやっているだけという可能性もありますが、まあそれにしても無駄はリスクに比べればそう大きなものでもないのでよしとしましょう。

<前 次>