2013年12月27日

不正落ちしたときにエラー個所を特定する(シンプル説明)

この話題すでに一度書いたのですが(http://junosoft.sblo.jp/article/60096788.html
無駄に遠回りな書き方をした気がするので、もっとシンプルに書いてみます(本当は自分のメモ用なんですけどね)



■アドレスからエラー個所を特定できるように、map, cod ファイルを生成するよう設定しておきます。これがないとどうにもなりません
・「プロジェクト」→「プロパティ」を開き、「構成プロパティ」→「C/C++」→「出力ファイル」を展開
・「アセンブリの出力」を「アセンブリ コード、コンピューター語コード、ソース コード (/FAcs)」に設定する
・この設定でビルドしたものを、ユーザーに配布する



■いざエラーが発生したら、ユーザーに以下の操作をしてもらい、「障害オフセット」の値を調べます

・イベントビューアを開く
WinXPの場合
スタートメニューを開いて、「コントロール パネル」「パフォーマンスとメンテナンス」「管理ツール」をクリックし、「イベント ビューアー」を開く

Win7の場合
スタートメニューを開いて「コントロール パネル」「システムとセキュリティ」「管理ツール」をクリックし、「イベント ビューアー] を開く

Win8の場合
Windows + X キーを押してメニューを開き、「イベントビューア」をクリックする

・イベントビューアの左側にあるツリービューから、「Windowsログ」の「Application」を開く
・イベント一覧が表示されるので、「日付と時刻」を頼りに、エラーを探す
・ダブルクリックするとイベントの内容が表示されるので「障害が発生しているアプリケーション名」や「障害が発生しているモジュール名」が目的のものであることを確認する
・「障害オフセット」の値をメモする



■障害オフセットの値がわかったら、それに対応するソースコードを探します
・「障害オフセット」の値に、ベースオフセット 0x00400000 を足して「エラーアドレス」を求める
・ビルドしたときに生成された .map ファイルを開く
・関数名と関数アドレス、その関数を含む .obj ファイル名の一覧が並んでいるので、「関数アドレス(Rva+Base と書いてある部分)」を頼りに、「エラーアドレス」を含む「エラー発生関数」を探す
・「エラーアドレス」から「関数アドレス」を引いて、「エラー個所のオフセット値」を求める
・「エラー発生関数」を含む .obj に対応する .cod ファイルを開く
・ソースコードとそれに対応するアセンブリコードが載っているので、そこから「エラー発生関数」を探す
・「エラー個所のオフセット値」を頼りに、エラーが発生した場所を特定する



以上の操作で、エラーが発生した関数名、ソースファイル名、行番号などがわかります
posted by JUNOSOFT at 21:55| Comment(0) | プログラミング

2013年12月23日

FPS制御

FPS制御の話です

例えば毎秒 60 フレームで固定したい場合、おそらく真っ先に思い付くのは

while (...) {
Sleep(1000/60);
ゲーム処理();
}

です。ところが Sleep() の引数はミリ秒単位の整数なので、
本当は 1000÷60=16.666... ミリ秒待機しないといけないのが 16 ミリ秒の待機になり、
1ループあたり 0.666...ミリ秒だけタイミングが早くなることになります。
すると実際の FPS は 1000÷16 = 62.5 になり、毎秒 62フレーム+αになってしまいます。

ただしこれは、ゲーム処理の時間と、Sleep関数の誤差を考慮しない場合の話です。
実際にはゲーム処理にはそれなりの時間がかかるし、Sleep関数も、指定した時間だけきっちり待ってくれるとは限りません。
というわけで非常にシンプルですが、このやり方はボツです。




次に思いつくのは1秒の間での更新スケジュールを決めておき、その時間になるまで待機する、という方法です。
つまり

DWORD baseTime = timeGetTime();
DWORD frameCount = 0;
while (...) {
 DWORD nextTime = baseTime + 1000 * frameCount / 60; // 更新予定時刻
 DWORD currTime = timeGetTime(); // 現在時刻
 if (currTime < nextTime) {
  Sleep(nextTime - currTime); // 現在時刻が、更新予定時刻より前ならば待機
 }
 if (baseTime + 1000 <= currTime) {
  baseTime = currTime; // 1秒ごとに基準時刻を更新
  frameCounter = 0;
 } else {
  frameCount++;
 }
 ゲーム処理();
}

という方法です。1秒ごとに基準となる時間を設定し、その時間からの経過フレーム数によって
更新時刻を決定します。これは一見うまくいくのですが、
実はこの方法、重い処理の直後に早送り状態にあるという欠点があります。
処理時間が一定していれば問題ありませんが、通常処理の中に重い処理があると、
予定よりも遅れた分を取り戻そうとしてウェイトなしの早送り状態になります。
この早送りは、予定遅れが解消されるか、基準時刻が更新されるまで続きます。
つまり最大でも1秒間なので、この挙動には目をつむるというのもアリです。




その挙動が気になる方のために、解決方法を考えてみます。早送り状態なってしまうのは、
無理に予定に合わせようとしているのが原因なので、遅延した場合はいさぎよく予定をあきらめ、
無理のないスケジュールを組みなおせばよいわけです。
これには、上のプログラムを少し改造して以下のようにします

DWORD baseTime = timeGetTime();
DWORD frameCount = 0;
while (...) {
 DWORD nextTime = baseTime + 1000 * frameCount / 60; // 更新予定時刻
 DWORD currTime = timeGetTime(); // 現在時刻
 bool delayed = false;
 if (currTime < nextTime) {
  Sleep(nextTime - currTime); // 現在時刻が、更新予定時刻より前ならば待機
 } else {
  delayed = true; // 遅延発生
 }
 if (baseTime + 1000 <= currTime || delayed) {
  baseTime = currTime; // 1秒ごとに基準時刻を更新
  frameCounter = 0;
 } else {
  frameCount++;
 }
 ゲーム処理();
}

という方法です。前回のフレーム計測基準時刻から1秒経過するか、または予定よりも遅れている場合に基準時刻を取り直します。
ただ、これまでは1秒ごとの基準時間更新時に frameCounter の値を記録することで実行FPS値を取得できたのですが、
この方法だと遅延が発生した場合に frameCount が毎回リセットされてしまうため、実効FPSを取得することができません。

いろいろいじりまわした結果、最終的には以下のような形になりました

DWORD FPS = 60; // 目標FPS
DWORD currentFPS = 0; // 実際のFPS
DWORD baseTime = timeGetTime();
DWORD lastTime = timeGetTime();
DWORD frameCount = 0;
while (...) {
 DWORD time = timeGetTime();
 if (baseTime + 1000 <= time) { // 1秒ごとに基準時刻を更新
  currentFPS = frameCount * 1000 / (time - baseTime); // frameCount回更新したときの所要時間から、1秒あたりの更新回数を求める
  frameCount = 0;
  baseTime = time;
 }
 DWORD next1 = baseTime + 1000 * frameCount / FPS; // 予定された更新時刻
 DWORD next2 = lastTime + 1000 / FPS; // 早送りにならないようにした時の更新時刻
 DWORD next = max(next1, next2); // 遅いほうの更新時刻に合わせる
 lastTime = time;
 if (time < next) {
  Sleep(next - time);
 }
 frameCount++;
 ゲーム処理();
}



当たり前のようにやっているFPS制御ですが、意外と深いです

posted by JUNOSOFT at 20:54| Comment(0) | プログラミング

2013年12月16日

VC2013

Visual C++ 2013 Windows Desktop を使ってみたのですが、ようやくBOMなしUTF8に対応したみたいですね〜〜!!
おかげでソースをUTF8化することができそうです。日本語コメント付きのソースを XCode と共有しているときに頭の痛い問題でした。それにSJISだと文字化けするエディタとかあるので...
入力補完もかなり頭がよくなってきて、快適に入力できるようになりました。GJ!
posted by JUNOSOFT at 11:35| Comment(0) | プログラミング

2013年12月04日

Hgで、問題の二分検索を使う

現在作業中のリビジョンでバグが見つかったとき、どの段階でそのバグが混入したのか調べるために
二分検索を良く使っていました。

たとえばリビジョン 100 で正常動作していたものが、リビジョン 200 ではダメだった場合、
ちょうど中間であるリビジョン 150 を試してみます。これが正常に動いたら 150 から 200 のどこかで問題が発生したことになるので、
今度は 150 と 200 の中間である 175 で試します。ここで問題がでた場合、150 と 175 のさらに中間である 162 で試します。
これを繰り返していくと、少ない試行回数で問題が発生した最初のリビジョンを特定することができます。

ということを手動でやっていたのですが、いまさらながら TortoiseHG にそのための機能があることに気が付きました。
TortoiseHG Workbench のリポジトリメニューにある「問題の二分検索」です。なぜ今まで気が付かなかったのかが不思議なくらい、わかりやすいタイトルです。

ここで、「既知の正常なリビジョン」に正しく動いていた時のリビジョン番号を入力し、「取り込む」をクリック。
つぎに、「既知の問題リビジョン」にダメなリビジョン番号を入力し、「取り込む」をクリック。最新リビジョンを指定したければ -1 と入力すればOK。
すると自動的に中間リビジョンに更新してくれるので、その状態で試した結果に応じて
「このリビジョンは問題ありません」か「このリビジョンは問題あります」のどちらかをクリックしていきます。
同じことを繰り返していけば、やがて「みつかりました!」みたいなメッセージがでて、問題が発生した最初のリビジョンを教えてくれます。

いやあ、便利なものですね。
posted by JUNOSOFT at 15:46| Comment(0) | プログラミング

2013年11月28日

Direct3DとOpenGLの行列変換

D3DXMatrix 系の関数で作成した行列を OpenGL に流用しようとしてハマったときの記録です。

例えば
D3DXMatrixTranslation(matrix, x, y, z);
で作った行列 matrix を glLoadMatrixf にセットする、という事をしたかったのですが、
「Direct3D と OpenGL の行列は転置関係にある」というのは分かっていたため、
あまり深く考えずに D3DXMatrixTranspose に通して転置し、それを glLoadMatrixf にセットしたのですが、うまくいきませんでした。

で、よくよく考えてみると、OpenGLの行列は平行移動の場合

m[0] m[4] m[ 8] m[12]   1 0 0 x
m[1] m[5] m[ 9] m[13] = 0 1 0 y
m[2] m[6] m[10] m[14]   0 0 1 z
m[3] m[7] m[11] m[15]   0 0 0 1

となり、一方 Dirext3D での平行移動は、D3DXMATRIX だと

_11 _12 _13 _14   m[0][0] m[0][1] m[0][2] m[0][3]   1 0 0 0
_21 _22 _23 _24 = m[1][1] m[1][1] m[1][1] m[1][1] = 0 1 0 0
_31 _32 _33 _34   m[2][1] m[2][1] m[2][1] m[2][1]   0 0 1 0
_41 _42 _43 _44   m[3][1] m[3][1] m[3][1] m[3][1]   x y z 1

となります。確かに転置関係にあるので、Direct3D の行列を OpenGL で使えるようにすれば転置すれば良いように思えるのですが、
ここでひとつ重要なことがあります。Direct3D の行列は2次元の配列であり、OpenGL の行列は1次元の配列として実装されているという事です。

そこで Dirct3D の行列における2次元配列をそのまま1次元配列と解釈してみると、

m[ 0] m[ 1] m[ 2] m[ 3]   1 0 0 0
m[ 4] m[ 5] m[ 6] m[ 7] = 0 1 0 0
m[ 8] m[ 9] m[10] m[11]   0 0 1 0
m[12] m[13] m[14] m[15]   x y z 1

となり、結局、行列成分の格納順序は Direct3D と OpenGL で変わらない、ということになります。
どちらの場合でも、x, y, z の移動量を指定するべき場所は m[12], m[13], m[14] です。

というわけで、例えば行列の作成に D3DXMatrix* 関数を利用し、それを OpenGL で使いたいなら、

D3DXMATRIX mat;
D3DXMatrixTranslation(mat, x, y, z);
glLoadMatrixf((float *)mat.m);

でよいし、逆に OpenGL 向けの行列を Dirext3D で使いたいなら

D3DXMATRIX mat;
glGetFloatv(GL_MODELVIEW_MATRIX, (float *)mat.m);
device->SetTransform(D3DTS_VIEW, &mat);

とすればよく、わざわざ転置処理する必要はない、ということです。

posted by JUNOSOFT at 06:29| Comment(0) | プログラミング

2013年10月31日

ツールでlua その3

ところで、対話形式で lua を使いたい時に、変数の中身を表示させるにはどうすればいいんでしょう?
たとえば変数 a の中身を見たい時に、わざわざ print(a) と打つのではなく、Python みたいに
a と打つだけで中身が表示されてほしいわけです。「必ず変数名が単独で入力される」という前提なら、

char line[256];
gets(line);
lua_getglobal(ls, line);
printf("%s", lua_tostring(ls, -1));

でも一応出来ますが、さすがに
a+2
とか
a[1]
とかって入力に対処できないのはひどすぎます。
luaL_dostring("a+2"); を実行した後にスタックトップに式の評価結果が
残っていてくれてれば話は早いのですが…

と思ったら、lua の i オプションでそういうことができるらしいです。
lua のソースを見てみたところ、luac.c にそれっぽいソースがありました。
lua を -i で起動すると args[has_i] に 1 が入ります。さらに追って行くと
dotty() という関数に行き着きました。どうやらここのソースを見れば目的の機能が
実現できるみたいです。みてみたら、やはり実行後にスタックに値が残っているみたいですね。
実行前のスタックサイズを 0 と仮定して、実行後にスタックにある値吐き出すようになっているみたいです。
今度、実際に試してみることにします。
posted by JUNOSOFT at 00:04| Comment(0) | プログラミング

2013年10月29日

ツールでlua その2

(つづき)

たとえば、ツール起動時に、DLL 側には編集画面を描画するためのウィンドウハンドルを渡します。
以後、編集中のアニメなんかは DirextX なり OpenGL なりを使い、指定されたウィンドウに対して描画します

ユーザーが編集画面内のある点をクリックして、部品を選択した場合は、その操作を lua スクリプトという形に変換して、それを DLL に渡して処理させます。たとえば、100, 120 をクリックしたなら

DoCommand("select_object_at(100, 120)");

のように。さらにそれをドラッグして 200, 160 に移動したなら、

DoCommand("move_object(200, 160)");

のようにします。
この方法の利点は、ユーザーの操作をすべてテキスト形式で記録しておく、ということが極めて自然に実現できるということです。マクロを作るのも簡単です。上記操作を一度にやりたければ、各命令の後ろに改行またはセミコロンを追加して

DoCommand(
"select_object_at(100, 120)\n"
"move_object(200, 160)");

などとすれば OK ですから。結構便利に使っていますよ〜

(微妙に続く)
posted by JUNOSOFT at 00:32| Comment(0) | プログラミング

2013年10月27日

ツールでlua

ツールはすべてコマンド文字列で操作できるようにしておき、(つまり対話型CUIでつくる)
それに GUI の皮をかぶせたら楽ができるのでは…と考えたのは数年前。

ツールの核となる部分は C++ でサクサク作りたい。
ツールの GUI は C# でサクサク作りたい。ならばどうすればよいか。
と考えたときに、核心部分をDLL化しておき、C# でつくったツール側からそれを呼び出せばよいという結論になりました。

さらに DLL に対する処理要求はすべて lua プログラム文字列という形で渡します。
DLL のインターフェースは、初期化、lua文字列を受け取って実行、内部状態を取得、内部状態を描画、終了処理。
ツール側で機能を増やしても、DLL のインターフェースを変える必要はなく、DLL 側に渡す lua 命令を修正するだけです

(つづく)
posted by JUNOSOFT at 07:55| Comment(0) | プログラミング

2013年10月23日

コマンドライン引数lua

ちょっとしたツールを作る場合、GUIをつけるのは面倒なので CUI のみで作っていたのですが、
コマンドライン引数の解析が面倒だし、どうせ自分しか使わないのでかなり適当な解析をしていました。

で、そういうのは lua に任せればいいじゃないかということに気づいてしまったのです。
コマンドライン引数の解析には getopt などがありますが、自分がいつも使っている lua で
実装したほうが楽だったのです。というわけで、これまでは

mytool.exe dest.dat src.dat

などとしていたものは、

mytool.exe arg("dest.dat", "src.dat")

となり、このコマンドライン引数をそのまま lua_dostring に渡せばよいということになります
ツール側には arg という関数を登録していれば OK と。あるいは単に

mytool.exe arg={"dest.dat", "src.dat"}

などとして lua_dostring した後に、lua_getglobal で arg の中身を得る、でもよいと。
...うん、まあ getopt 使っとけって話ですね
posted by JUNOSOFT at 00:14| Comment(0) | プログラミング

2013年10月18日

アクセント記号付きのアルファベット


とあるフリーの辞典ソフト向けに配られている外国語辞書データを調べていたんですが、そのテキスト形式のデータはSJIS と Latin-1 が混在していたので最初はすごく戸惑いました。テキストエディタをどう設定しても文字化けが解消されないぞと。というか Latin-1 なんてまったくなじみがないですよね。バイナリエディタで文字コード調べて、ようやく気がつきました。

見出し文字や例文などはアクセントやウムラウトつきのアルファベットに対応する為に Latin-1 コードで、日本語での解説部分は SJIS になっていたのです。というわけで、欧州言語の基本的な文字コードは Latin-1 です。覚えておくことにします。

イラっとしたので、例の辞書データは Python スクリプトで一括処理し、全部 UTF-8 に統一してやりました。すっきりです。あとは入力文章に出現した単語に対応する日本語説明文を並べて表示するだけで、簡易直訳ツールの出来上がりです。なんせ翻訳ツールによっては、下手に日本語らしい文章にしようと努力されるよりも、直訳で表示してくれた方がよほど分かりやすい場合がありますから…

posted by JUNOSOFT at 15:43| Comment(0) | プログラミング

2013年10月13日

プログラム用フォント

突然ですが、フォントの美しさというのは、開発意欲に少なからず影響を与えると思っています。
きたないフォントだとやる気もなくなりますからね。
ところがプログラムに使えるフォントとなると、それは自動的に固定ピッチフォントになるわけで、あまり数がないんですよね。
そのうえ日本語にも対応してないといけませんし。読みやすさも考えると結局 MSゴシック, Consolas, Lucida, ぐらいしか選択肢がありません。

そんなわけで、メイリオを固定ピッチ化したという救世主的なフォントの登場です。

http://jutememo.blogspot.jp/2013/03/windows-meiryoke-mactype.html

しかしこれ、自分の環境 WinXP+VC2008 では快適に使えてたのに、WinXP/Win7+VC2010 だと文字が表示されないという問題がありました。
さらに Win7 で使ってみようとしたらフォントがうまく生成されなかったという…. VC2012 ではそのへんどうなっているのでしょうか。
プログラマ向け日本語フォントは難しいですね。
posted by JUNOSOFT at 20:44| Comment(0) | プログラミング

2013年10月09日

頂点カラー

これまで頂点には D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1 を使っていたのですが、
2Dゲームを作っていると、四角形ポリゴンの4頂点には全て同じ頂点カラーを適用することがほとんどです。
そうしないのはグラデーションを作りたいときぐらいですから。そうすると、頂点ごとに個別のカラー設定をしているのが無駄に思えてきます。
実際そのせいで1頂点に付き4バイトが消費されているわけで、わざわざ各頂点ごとにカラー情報をもたせる必要はないよな、
ということで D3DFVF_DIFFUSE を頂点定義から外し、D3DTSS_CONSTANT / D3DTA_CONSTANT で定数色を設定するようにしました。

ところが、これでつくったプログラムを配布したとところ、なんか表示がおかしいという報告がいくつもありました。
D3DTSS_CONSTANT が使えるかどうかを判別するには D3DPMISCCAPS_PERSTAGECONSTANT を使うのですが、
どうやら思った以上にサポートしていない環境が多いみたいです。

http://www.kludx.com/capability.php?capability=97

で、よくよく考えてみれば D3DTSS_CONSTANT はマルチテクスチャ用なわけで、
マルチテクスチャ使わないなら素直に D3DRS_TEXTUREFACTOR / D3DTA_TFACTOR を使っておけばよかったんですね。
一件落着。
posted by JUNOSOFT at 16:08| Comment(0) | プログラミング

2013年09月19日

Direct3DとOpenGLで頂点宣言を共有したい

Android 向けと Windows 向けのプログラムを書いているので、グラフィック関連のインターフェースはなるべく同じにしておいて、OpenGL と Direct3D のどちらを使っているのか意識しなくても大丈夫なようにしたかったのですが、なんせ OpenGL と Direct3D では流儀が異なるため、そのままではうまくいきません。

とはいえ、行列の Colmun major order と Row major order の違いは単に転置すればよいだけだし、右手系左手系なんかもスケーリング行列を挟むだけで違いは吸収できるし、楽勝じゃん!!とか思っていたのですが、ただひとつ、頂点の構造体をつくりたいときに、色設定が Direct3D では D3DCOLOR (32ビット符号無し整数)なのに対し、OpenGL が float*4 になっていることが悩みでした。

使い勝手を考えると、Direct3D 側で色設定が float*4 でできるようになるのが一番なのですが、D3DFVF 列挙にはそういった指定が見当たりません。


と思ってたら、D3DFVF なんて使わないで IDirect3DVertexDeclaration9 使えばよかっただけみたい…


そんなわけで、例えば頂点として

struct VERTEX {
 float x, y, z;
 float r, g, b, a;
 float u, v;
};

を使うなら、Direct3D 利用時には


void DrawTriangleStripDX(const VERTEX *vertexArray, int numVertices) {
 D3DVERTEXELEMENT9 elm[] = {
  {0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
  {0, 12, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0},
  {0, 28, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},
  D3DDECL_END()
 };
 IDirect3DVertexDeclaration9 *decl;
 g_D3DDevice->CreateVertexDeclaration(elm, &decl);
 g_D3DDevice->SetVertexDeclaration(decl);
 decl->Release();
 g_D3DDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, numVertices-2, vertexArray, sizeof(VERTEX)));
}

みたいに呼び出せばいいし、OpenGL 使用時には

void DrawTriangleStripGL(const VERTEX *vertexArray, int numVertices) {
 glEnableClientState(GL_VERTEX_ARRAY);
 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
 glEnableClientState(GL_COLOR_ARRAY);
 glVertexPointer (3, GL_FLOAT, sizoef(VERTEX), ((const BYTE*)vertexArray) + 0);
 glColorPointer (4, GL_FLOAT, sizoef(VERTEX), ((const BYTE*)vertexArray) + 12);
 glTexCoordPointer(2, GL_FLOAT, sizoef(VERTEX), ((const BYTE*)vertexArray) + 28);
 glDrawArrays(GL_TRIANGLE_STRIP, 0, numVertices);
}

のようにすれば、同じ形式の頂点を OpenGL でも Direct3D でも使うことができる…と。
なんかすごく遠回りした気分。
posted by JUNOSOFT at 12:13| Comment(0) | プログラミング

2013年09月06日

ファイル名が欲しい5

今回は、ソート機能を追加してみようと思います


■ソートメニューを追加
・フォームを選択した状態で、「ソート(&S)」メニューを追加する


■パスを昇順(A→Z)でソートする
 配列をソートするには Array.Sort を使います

・「ソート(&S)」にサブメニュー「昇順(A→Z)」を追加
・private void 昇順AZToolStripMenuItem_Click(object sender, EventArgs e) {} というメソッドが追加されるので、
 そこに以下のコードを追加
 
  string[] lines = (string[])textBox1.Lines.Clone();
  Array.Sort(lines);
  textBox1.Lines = lines;
 

■パスを降順(Z→A)でソートする
 昇順ソート同じですが、最後にリストを逆順にします
 
・「ソート(&S)」にサブメニュー「降順(Z→A)」を追加
・private void 昇順AZToolStripMenuItem_Click(object sender, EventArgs e) {} というメソッドが追加されるので、
 そこに以下のコードを追加

  string[] lines = (string[])textBox1.Lines.Clone();
  Array.Sort(lines);
  Array.Reverse(lines);
  textBox1.Lines = lines;


■パスを拡張子順でソートする
 単純比較できない場合のソートには、比較用クラスを用意します
 
・「ソート(&S)」にサブメニュー「拡張子 昇順(A→Z)」を追加
・private void 拡張子順ToolStripMenuItem_Click(object sender, EventArgs e) {} というメソッドが追加されるので、
 そこに以下のコードを追加

  string[] lines = (string[])textBox1.Lines.Clone();
  Array.Sort(lines, new ExtComp());
  textBox1.Lines = lines;

・このままでは比較クラス ExtComp が存在せずコンパイルできないので、適当な場所に ExtComp クラスの定義を追加

  private class ExtComp : System.Collections.IComparer {
   public int Compare(object x, object y) {
    string ext1 = System.IO.Path.GetExtension((string)x);
    string ext2 = System.IO.Path.GetExtension((string)y);
    return String.Compare(ext1, ext2);
   }
  }

これで実行すると、たしかに拡張子順にソートされることが確認できます
posted by JUNOSOFT at 00:56| Comment(0) | プログラミング

2013年09月01日

始点1ピクセルが描画されない

ちょっと小ネタ

四角形のアウトラインを描画するときに、ラインを4本引いて四角形にしているのですが、
左上の1ピクセルが常に欠けているのがすごく気になってました。
以下のコードでいうと、ドットバイドットで描画したときに (x0, y0) の1ピクセルが必ず欠けます。
直線描画のときだけ欠けるので、ステンシルマスクやZバッファの問題ではなさそうです。

struct VERTEX {
 float x, y, z;
};
const VERTEX v[] = {
 {x0, y0, 0},
 {x1, y0, 0},
 {x1, y1, 0},
 {x0, y1, 0},
 {x0, y0, 0}};
device->SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE);
device->DrawPrimitiveUP(D3DPT_LINESTRIP, 4, v, sizeof(VERTEX));

終点の1ピクセルが描画されないのであれば D3DRS_LASTPIXEL が FALSE になっているのかな、とも思うのですが、
なぜか終点ではなく始点です。というか、仮に始点が描画されない設定になっているなら4本目の終点としての x0, y0 が、
終点が描画されない設定になっているなら、1、2本目の始点としての x0, y0 が、始点終点両方とも描画されない設定になっているなら、
x0, y0 だけでなく4頂点すべてが描画されないはずです。謎です。

どうも納得がいかないのですが、結局太さ1ピクセルの超細長い長方形ポリゴンを描画して直線の代わりにしました。
拡大したときに直線の太さが1ピクセルでなくなってしまいますが、ドットバイドットが基本のゲームなのでそこは問題無しとします。釈然としませんが…
posted by JUNOSOFT at 11:42| Comment(0) | プログラミング

2013年08月19日

ファイル名が欲しい4

今回は、複数のファイル名に対する一括操作を追加してみようと思います


■一括操作用のメニューを追加
・フォームを選択した状態で、「一括操作(&O)」メニューを追加する


■ファイル名だけを抜き出す(ドライブ名、ディレクトリ名部分を削除する)
 ファイル名を取り出すには System.IO.Path.GetFileName を使います

・「一括操作(&D)」にサブメニュー「ファイル名を抽出」を追加
・private void ファイル名を抽出ToolStripMenuItem_Click(object sender, EventArgs e) {} というメソッドが追加されるので、
 そこに以下のコードを追加
  for (int i=0; i<textBox1.Lines.Length; i++) {
   textBox1.Lines[i] = System.IO.Path.GetFileName(textBox1.Lines[i]);
  }
 
 ところが、このコードだとコンパイルは通るものの、うまくいきません。
 Delphi だとこの方法を使って行単位で書き換えることができたのですが、どうやら C#(というより .NET Framework)での Lines プロパティは読み取り専用…というかただの配列のようです。
 というわけで、文字列配列を作成して一気に Lines プロパティに代入するように変更します

  string[] lines = (string[])textBox1.Lines.Clone();
  for (int i = 0; i<lines.Length; i++) {
   lines[i] = System.IO.Path.GetFileName(lines[i]);
  }
  textBox1.Lines = lines;

 あるいは単に、単一の文字列を生成してから Text プロパティに代入してもOKです。
 
  string text = "";
  for (int i=0; i<textBox1.Lines.Length; i++) {
   text += System.IO.Path.GetFileName(textBox1.Lines[i]) + System.Environment.NewLine;
  }
  textBox1.Text = text;


■拡張子を削除する
 拡張子の削除は System.IO.Path.ChangeExtension を使います
 
・「一括操作(&D)」にサブメニュー「拡張子を削除」を追加
・private void 拡張子を削除ToolStripMenuItem_Click(object sender, EventArgs e) {} というメソッドが追加されるので、
 そこに以下のコードを追加
 
  string[] lines = (string[])textBox1.Lines.Clone();
  for (int i = 0; i<lines.Length; i++) {
   lines[i] = System.IO.Path.ChangeExtension(lines[i], null);
  }
  textBox1.Lines = lines;

 Win32Api の PathRenameExtension ではピリオドを含む拡張子を対象としていましたが、System.IO.Path.ChangeExtension はピリオドを含まない部分を対称にするので気をつけましょう
 もし System.IO.Path.ChangeExtension("filename.ext", ""); としてしまうと、末尾にピリオドが残ってしまいます



次回はソート機能を追加します
posted by JUNOSOFT at 21:15| Comment(0) | プログラミング

2013年08月12日

ファイル名が欲しい3

前回作った雛形で、「ファイルをドロップしてファイルパスを得る」 という、最低限欲しかった機能がすでに付いているのですが、あまりにも使いづらいので、そのあたりを改善してみようと思います。

■長い行が勝手に折り返されてしまう
 ファイル名の一覧などは1行1項目が見やすいのですが、デフォルト状態だと長い行が右端で折り返されて見づらいため、まずはこの機能をOFFにします
 
 ・フォーム上に配置してあるテキストボックスを選択
 ・メニューから 表示→プロパティウィンドウ を選択してプロパティウィンドウを表示
 ・プロパティウィンドウの WordWrap を False に変更


■複数行表示したとき、文字の位置がガタガタになる
 ファイル名はピリオドなどの小さな記号を含んでいますし、似たようなパス名を持つであろうファイル名を複数表示するわけですから、縦方向(列方向)に文字の位置がそろっていたほうが何かと見やすくなります
 そんなわけで、デフォルトで設定されている可変ピッチフォント(プロポーショナルフォント」)はあまり良くありません。
 これを固定ピッチフォント(等幅フォント)に変えてみましょう。

 ・フォーム上に配置してあるテキストボックスを選択
 ・プロパティウィンドウの Font をクリック
 ・サブ項目が展開されるので、そこにある Name 項目の値を 「MS ゴシック」に変更
 ・この状態で実行(F5)し、フォントが変化していることを確認


■メニューを追加する
 これからさまざまな機能を追加するので、今のうちにメインメニューを追加しておこうと思います
 
 ・ツールボックス(無ければ表示→ツールボックスで表示)の「メニューとツールバー」にある MenuStrip をダブルクリック
 ・フォームに空っぽのメニューが追加されるので、「ここへ入力」と書いてある場所に 「ファイル(&F)」と入力(&Fは必ず半角にする。&記号を忘れずにつけておくこと)
  → & と F を並べて書いておくと F に下線がつき、Alt+Fを押すことでメニューが選べるようになる
 ・同じ要領で「ファイル(&F)」の下にサブメニュー「終了(&X)」を追加
 ・この状態で実行(F5)し、ウィンドウにメニューがちゃんとついていることを確認


■メニューを選択したときの動作を追加する
 いま追加したメニューは見た目だけで、選んでも何もおきません。というわけで、メニューが選択されたときの動作を追加します

 ・「ファイル(&F)」をクリック
 ・サブメニューが出るので、「終了(&X)」をダブルクリック。これにより 終了XToolStripMenuItem_Click のような名前のメソッドが追加される(メニューを追加したときに最初に入力した名前から記号を除いたものが、メソッド名の先頭に勝手に付く。今回は「終了(&x)」という名前を入力していたので、先頭に終了X])
 ・追加された 終了XToolStripMenuItem_Click メソッドの中に

  Close();

  という行を追加
 ・この状態で実行(F5)し、ファイル→終了メニューでウィンドウが閉じることを確認


■少しはマシになった?
ひとまず、これで土台が完成しました。最低限の機能と、最低限の見た目を備えています。
次からはファイル名に対する一括処理の機能を追加していくことにします。
posted by JUNOSOFT at 09:43| Comment(0) | プログラミング

2013年08月07日

ファイル名が欲しい2

とりあえず C# を使ってファイル名取得ツールの土台を一気に作ってみようと思います。
必要な手順だけを箇条書きにしたので、参考にしてみてください。


■プロジェクトの新規作成
・まず C# を起動
・「ファイル」→「新しいプロジェクト」→「Windowsフォームアプリケーション」を選択し、適当なプロジェクト名を入力して決定
・この状態で実行(F5)し、なにもないフォームが表示されることを確認


■テキストボックスを追加
・ツールボックス(表示→ツールボックス)から TextBox を選んでダブルクリックし、フォーム上に1行サイズのテキストボックスを配置
・配置されたテキストボックス上で右クリックし、メニューからプロパティを選択
・プロパティ一覧の MultiLine を True に変更(複数行の編集に対応させるため)
・プロパティ一覧の Dock を Client に変更(ウィンドウいっぱいの大きさにし、ウィンドウサイズの変更に対応させるため)
・この状態で実行(F5)し、テキストが自由に編集できることを確認


■ファイルのドラッグドロップを受け入れる
・フォームを選んだ状態でプロパティを出し、AllowDrop を True にする(ドロップ動作を許可するため)
・フォームを選んだ状態でプロパティを出し、雷マークのついたボタンをクリックし、イベント編集モードにする
・イベント一覧から DragDrop をダブルクリックしてイベントを追加。これにより Form1_DragDrop というメソッドが追加される
・追加された Form1_DragDrop メソッドに
  e.Effect = DragDropEffects.All;
 という行を追加

・フォームのイベント一覧から DragEnter をダブルクリック。これにより Form1_DragEnter というメソッドが追加される
・追加された Form1_DragEnter メソッドの中に
  if (e.Data.GetDataPresent(DataFormats.FileDrop)) {
   foreach (string path in (string[])e.Data.GetData(DataFormats.FileDrop)) {
    textBox1.Text += path + System.Environment.NewLine;
   }
  }
 という行を追加
 
■確認
あっけないかもしれませんが、以上で基本形は出来上がりです。F5で実行し、適当なファイルをドラッグドロップしてみてください。もちろん複数ファイルにも対応しています。

次回、もう少し機能を追加していくことにします
posted by JUNOSOFT at 05:23| Comment(0) | プログラミング

2013年08月04日

ファイル名がほしい


プログラムをしていると、フォルダ内のファイル名一覧がテキスト形式で欲しくなる時があります。
もちろん、適当なフォルダをドロップするとファイル名一覧を作成してくれるツールはいくつかあるのですが、自分にとっては少し使いにくかったので、簡単なツールを作ってみることにしました。
ついでにその製作過程を載せておきます。

まず、欲しい機能は以下のとおり。

・見た目はメモ帳みたいにシンプルで、テキストを自由に編集できる
・そこにフォルダをドロップすると、フォルダ内の全てのファイル名がフルパスで追加される
・そこにファイルをドロップすると、そのファイル名がフルパスで追加される
・カーソル行のファイル(フォルダ名ならそれを、ファイル名ならその親フォルダを)エクスプローラで開くことができる
・カーソル行のファイルを OPEN できる
・以下の操作を、すべての行または選択された範囲内に対して一括で適用できる
  ファイル名だけを抽出(=パス部分を削除)
  拡張子を削除
  拡張子を変更
  実際に存在するファイル名だけを残し、あとは削除
  ファイル名順でソート
  拡張子順でソート
  末尾に \ がなければ、追加
  末尾に \ があるなら、削除
  ファイルに対してリンクするようなHTMLソースを生成

以上の要求を満たすツールをC#でサクッと作ってみたいと思います
posted by JUNOSOFT at 18:49| Comment(0) | プログラミング

2013年07月30日

ランダムでアイテムを落とす

敵を倒すとランダムで一つのアイテムを落とす、という動作を実装したいとき、真っ先に思いつくのは敵が死ぬたびに乱数を取得し、その値に応じたアイテムを落とすというやり方です。

例えば 10%の確率で宝箱、90%の確率でコインを落とさせたいときには、

if (GetRandomInteger(100) < 10) {
DropTresure();
} else {
DropCoin();
}

みたいにするわけですが、これはあまりよくないやり方です。毎回乱数を使ってアイテムを決めると、実感としてかなり偏った出方をします。宝箱が10%の確率でしか出現しないはずなのに、4回ぐらい連続で出てしまったりとか、逆に40回ぐらいやっても宝箱が一つも出現しないとか、わりとよくあります。


この場合は考え方を変えて、毎回乱数で抽選するのではなく、「あと何回敵を倒したら宝箱が出るか」を決めてやります。
そうすれば、例えば「次の宝箱が出現するまで最低4回、最長で20回敵を倒す必要がある」のような決め方ができるので、比較的意図通りの出現パターンになります。ちなみに「最低1回、最大10回」は出現率10%ではないので注意してください。この場合、アイテムの出現するまでの試行回数は (1+2+3+4+5+6+7+8+9+10) / 10 = 5.5 で期待値5.5になり、5.5回に1回の割合で出現するようになるため、出現率は 1 / 5.5 = 18%になってしまいます。


もっと細かく制御したければ、敵が落とすものを100回程度先まであらかじめ決めておく、といった方法もあります。
これなら完全に思い通りのタイミングと順番で宝箱を出すことができます。

posted by JUNOSOFT at 09:26| Comment(0) | プログラミング