時乃工房-Windowsとアマグラマーな関係-

アクセスアップにオートリンクネット リンクが自動で増殖オートリンクの登録はこちら 節約内職情報検索局
ページランク向上リンク集
役立つリンク集 Web Links
SEO対策ディレクトリ型検索エンジン Su-Jine
メニュー
トップ
アマグラミング
C++編 目次
第21章
ソフトウェア開発製品
相互リンク

<勘違いだらけのアマグラミングな日々(C++編)>

第22章 気まぐれと乱数の違い

2007年1月10日

多少なりともプログラムを組んでみたことのある人なら、乱数の二文字を聞いたことがあると思う。
ただ、私もそうだったのだが、乱数とは何かを結構誤解している人が多いのも事実だ。
私は長い間、乱数とは規則性の無い数字の羅列であると思っていた。
ところが先日読んだ数学の本によると、乱数とは、規則性を持たず再現性のある数字の羅列、と定義されているようなのだ。
私は数学が非常に苦手な為、もしかしたらその本の著者の云わんとするところを誤認識している可能性もあるが、物理シュミレーションを行うような状況で利用される乱数は、幾度も同一条件を生み出す必要があるため、再現性が必要になってくるらしい。
興味がある方は、メルセンヌ・ツイスター法などの有名なアルゴリズムについて調べてみると、結構楽しめるんじゃないかな、と思う。
ただ、プログラミングにおける乱数は、アルゴリズムが存在する限り規則性を持つため、必ず擬似乱数となってしまう。
まぁ、アルゴリズムは規則の定義でもあるから、これはどうしようもない。

冒頭にいきなり乱数の話題を持ってきたのは、キャラクターを自分勝手に行動させるもっとも簡単な手段は、ランダム、であろうと思ったから。
乱数によって偶然(?)与えられた値をもとにした行動は、おそらく自分勝手でわがままな行動に見えるはずである。

もちろんC言語で定めるところの標準関数の中に、関数rand()が用意されている。
これは線形合同法と云われるアルゴリズムによって乱数を生成する関数で、パチンコのルーレットに使うにはちょっと問題があるが、今回のようなさして乱数の精度が問題にならないアプリケーションには最適なものだ。
しかし、今回のアプリケーションではC言語の標準関数は使えない
いや、使えることは使えるんだが再入不可能な関数が大多数を占めているため、複数のスレッドから同時に使用してはいけないことになる。
マルチスレッド対応のライブラリを利用するか、マイクロソフトのマネージ拡張によるスレッド管理を導入することでこの問題を解決できるようだが、今回は思い切って乱数発生の関数を自前で用意することにした。

List.003-007
main_window.h
main_window.cpp
main_callback.h
main_callback.cpp
main_process.h
main_process.cpp
mathmatical_functions.h
mathmatical_functions.cpp
environment_control_h
environment_control_cpp
character_control_procedures.h
character_control_procedures.cpp
graphics_control.h
graphics_control.cpp
bitmap.rc
main_menu.h
main_menu.cpp
main_menu.rc
<ソースのダウンロード>

乱数を発生させる関数GetRandomNumber()を用意するにあたって、計算用の関数郡をまとめる為のmathmatical_function.cppを授けた。
作った関数GetRandomNumber()は、昔PC−88○1で作った1バイト乱数発生アルゴリズムを基にしたもので、もっとも単純な線形合同法を利用したものだ。
周期も非常に短くて、255個の数字を吐き出すと0に戻って、また同じ数列を吐き出し始める。
ただし、この関数から得られる数字を利用するにあたって一つだけ注意していただきたいことがある。
中心となる計算式
number= 5* number+ 1;
であることからご想像いただけると思うが、この関数GetRandomNumber()からは、奇数と偶数が交互に規則正しく生成される。
従って、奇数か偶数かで2択を行わせるようなコードを書くと、交互に選択されてしまうことから、結果ランダム(っぽい)な挙動が期待できないためNGである。
さて、このアルゴリズムからどんな順番で数字が取り出されるかだが、実際計算してみてもらえば直ぐに判るので説明は省略
0から255までの数字がだぶることなく順番に(?)出てくるのを、是非確認してみていただきたい。
ちなみに、これも私が考えたアルゴリズムでは無かったりする。

次に、この関数GetRandomNumber()を利用してタコ助のコントロールを司る関数EnemyControlProcedure()を書き換える。
メッセージCSM_STOPの処理として、キャラクタータコ助が停止したところで、関数GetRandomNumber()によって得られた擬似乱数をもとに適当な移動先を算出して、タコ助の目標座標として再び移動を始める仕掛け。
・・・正直、あんまり綺麗なコーディングではないが、今に始まったことではないので勘弁してください。

さて、これを実行すると、クライアントエリア左上端で発生したタコ助は、あたかも勝手気ままに画面内を移動し始める。
もちろんマウスによる左クリックも認識するため、クリックして目標座標を指示してやるとその座標に立ち寄って、また、勝手気ままな移動を繰り返す。
タコ助はこうして自由を得ることに成功したのである。

さて、立派に自立を遂げたタコ助君。
自由になっても天涯孤独では寂しいだけなので、次は沢山のタコ助君を生成して、クライアントエリアを賑やかにしてやろう。

List.003-008
main_window.h
main_window.cpp
main_callback.h
main_callback.cpp
main_process.h
main_process.cpp
mathmatical_functions.h
mathmatical_functions.cpp
environment_control.h
environment_control.cpp
character_control_procedures.h
character_control_procedures.cpp
graphics_control.h
graphics_control.cpp
bitmap.rc
main_menu.h
main_menu.cpp
main_menu.rc
<ソースのダウンロード>

まず、このソースをビルドしてもタコ助が現れないことを先に述べておこう。
今回の変更点は、CHARACTER構造体配列を用意してやったこと。
配列の大きさは256個としたので、タコ助256匹分の構造体を用意したことになる。

これに合わせてCHARACTER構造体にも、一つ要素を増やしてやった。
BOOL life;
がそれで、このキャラクターが存在しているか否かを示すフラグとして利用する。
関数MoveEnvironment()内でこのフラグlifeを判断して、行動を行うか否かを判断しているわけで、本サンプルではフラグlifeTRUEをセットしていないため、画面上にタコ助が現れないわけである。

次に構造体enemyを配列化した事に関連して、必要な箇所を書き換えてやらなければならない。
この変更が、はっきり云って面倒くさい
変更箇所も多く見落としが発生するため、コンパイラに怒られてばかりの作業となってしまった。
変更を施した関数は以下のとおり。
目標座標の指定関数SetTargetPosition()
キャラクターの行動処理関数MoveEnvironment()
キャラクタービットマップのセット関数SetCharacterBitmap()
コントロールプロシージャのセット関数SetControlProcedure()
・・・つまりenvironment_control.cppのほとんどの関数を変更したわけ。

まず、比較的お気軽なものとして、関数MoveEnvironment()除く3つから。
これらには、引数にint型の変数character_handleを追加してやった。
こうして各処理を特定の構造体に対して行えるようにしたわけだ。
次に関数MoveEnvironment()への変更。
これまでの処理をまんまforループで囲って、256個の構造体enemyに対して同じ処理を行うようにしている。
ただ、前出フラグlifeTRUEでない(つまり存在していない)キャラクターに対して処理を行うのは無駄なので、if文による分岐によって処理をとばすようにしてある。

さて、せっかく行った変更もこのままでは何の役にも立たないので、次は目に見える成果を得られるよう手を加えてみた。

List.003-009
main_window.h
main_window.cpp
main_callback.h
main_callback.cpp
main_process.h
main_process.cpp
mathmatical_functions.h
mathmatical_functions.cpp
environment_control.h
environment_control.cpp
character_control_procedures.h
character_control_procedures.cpp
graphics_control.h
graphics_control.cpp
bitmap.rc
main_menu.h
main_menu.cpp
main_menu.rc
<ソースのダウンロード>

変更点の紹介を始める前に、実行画面の様子を左図にあげておこう。
まず起動直後、クライアントエリアには背景のみが表示されている。
次に、クライアントエリア内の任意の座標を左クリックすると、そこにタコ助が現れる。
こいつはList.003−007で用意したコントロールプロシージャによって、勝手気ままに移動を始める。
また別の座標を左クリックすると、そこにも新たなタコ助が現れる。
これも同じコントロールプロシージャによって、めちゃめちゃな軌道を描きながら移動を始める。
こうしてこのアプリケーションでは、256匹までのタコ助をクライアントエリア内に放すことが出来るようになっている。
いやしかし、こうして複数のタコ助が別々に行動しているのを見るのもなんだか感慨深いなぁ。(手前味噌ですいません)

では変更点の紹介を。
main_process.cpp内においては、左クリックしたときの処理を行う関数SetPosition()を変更。
この変更を行うまでは、タコ助の行き先を指示していたわけだが、今度はクリックされた座標にタコ助を出現させるように変更した。
この変更箇所にある新たな関数CreateCharacter()が、タコ助を生成するもので、environment_control.cppにて用意した。
なにを行っているのかと云うと、引数として渡された要素、キャラクタの座標と使用するビットマップハンドル、そしてコントロールプロシージャのポインタ構造体enemyへの代入
ついでにenemy[].lifeにTRUEをセットして、このキャラクターの存在を有効にしている。
しかしてこれらの処理はforループによってくくられていて、構造体enemy[0]からenemy[255]までのなかでenemy[].lifeがFALSEのものを探し出し、見つけた構造体に上記の処理を行ったうえでその配列番号をint型のハンドルとして返している

最後にもう一度main_process.cppに戻って、関数SetupProcess()内で不要となった処理を削除して出来上がり。
晴れてタコ助はクライアントエリアに溢れかえるほどの繁殖を・・・はちょっと無理だった。
少なくとも、私の環境では10匹程度が限界で、これ以上増やすと処理が重すぎて、タコ助が動いているようには見えない。

次回ではもう少し実用的な速度に調整できるといいな、というところで今回はここまでとしよう。
・・・速度のネックになるのはいつも遅いデバイスなんだよね。

<前章> <目次>





<時乃工房>
Net Office Nakai
メビウスリング投稿掲示板には小説日記ゲームアニメコミック小学生中学生などの掲示板過去ログがあります。相互リンクも募集中。