[前へ]   [目次へ]   [次へ]

単語当てゲームLv1の設計(C/C++)


   (10/02/05) 解説図を作りなおしました

今回は、作成するゲームの具体的な設計を考えてみます。
現時点までで決まっているのは、

   ソース上に直接書き込んだ問題を乱数を使って選択し、表示します。
   その問題の答えをプレイヤーはキーボードから入力し、成否を判定します。

というかなりアバウトな状態になっています。
もっと具体的にしておかないとプログラムを作れないので、
今回はこれを具体化していく作業を行います。

このあたりの設計に関しては昔からいろいろな方法が提案されていますが、
「これぞ」といった方法は現在でもいま一つ確立しきれていないところがあるように感じます。
(今現在有力と思われるのはUMLという記法を使う方法だと思います。私自身は使ってないんですけど。)
私のやり方は基本的に我流だったりするので、今回の内容が最良であるとは言いません。
とはいえ根本は同じ部分があるので、参考にはなるはずです。


さて、そろそろ本題に入ります。
まず、最初の文を分解して、必要な処理や要素を抽出します。
ここからの過程で、不足している仕様が明らかになってくるので、それらを適宜補いつつ、進めていきます。
↑の仕様を分解すると、例えば以下のようになります。

ソース上に直接書き込んだ問題を
乱数を使って選択し表示します
その問題の答えをプレイヤーはキーボードから入力し
成否を判定します

次に、これらを「機能」「要素(情報やデータ)」に分けます。

[要素]ソース上に直接書き込んだ問題を
[機能]乱数を使って選択し表示します
[機能]その問題の答えをプレイヤーはキーボードから入力し
[機能]成否を判定します

「要素」はプログラム上で扱うことができる、
あらゆるデータや情報を指します。
これらは変数やオブジェクト、ファイル、デバイス、ネットワーク等も含まれます。

「機能」はそのプログラムが実現する機能を指します。
「どういうことがしたい」というものが「機能」になっていきます。
今回はこの部分まではある程度具体化されており、「機能」の分解はこれ以上必要ありません。

また、「機能」をどのように表記、分解すべきかですが、
基本的に、「ユーザが何かするところ」や「時間経過」「待ち合わせ」など、
処理が一時的に待機したり、操作待ちをする場所を区切りにして分解していくと比較的まともに切れます。

表記については機能の複雑さにもよりますが、階層構造を持たせて表記し、
下位にいくほど具体的に記述するようにします。
この段階で具体的なプログラムを意識しておくに越したことはありませんが、
実現できそうかどうか程度に留め、全体として矛盾のない構造を構成した方がいいでしょう。

今回程度の複雑さでは階層化しようがないので今回は行いません。
いつかどこかである程度の規模のものを出したいと思いますが・・・


次に、「機能」から「状態」と「処理」を抽出します。
具体的に作成するプログラムは「処理」に基づいて作成することになります。

「状態」は「機能」の「次に行うべき内容」を示すものです。
下位の「機能」を持つ機能の場合、「状態」は次に起動する「機能」を示す場合が多く、
最下位の「機能」の「状態」は、「処理の途中経過」を示す場合が多いです。

「機能」の階層化や分解が正しく行えていれば、
この段階で「状態」が大量に出現することはないはずです。

もし別の「機能」の「状態」を参照しなければ次の「状態」を定義できないなど、
複雑な関係を持ったり、「状態」が大量発生してしまうような場合、
「機能」の階層化や分解に失敗している可能性が高いです。

この場合は前の手順に戻り、構成を再考した方が無難です。
無理に進めると手順が進むにつれて指数的とまでは言わないまでも、
爆発的に複雑さが増していき、プログラムは混沌の海へと沈む可能性が高いです。


今回の場合、単純なため「状態」はありません。「処理」が主になります。

[機能]乱数を使って選択し表示します
   [処理]乱数を発生させる
   [処理]問題を選択する:「[要素]選択された問題(未定義)」を作成
   [処理]選択された問題を表示する
[機能]その問題の答えをプレイヤーはキーボードから入力し
   [処理]プレイヤーから答えが入力されるのを待つ:「[要素]入力された答え(未定義)」へ結果を出力
[機能]成否を判定します
   [処理]結果を判定して分岐:正解なら「[機能]クリア表示して終了(未定義)」へ進む、
                         間違いなら「[機能]その問題の答えをプレイヤーはキーボードから入力し」へ進む

未定義の「[機能]クリア表示して終了」が現れたので、作成します。
未定義の要素は後で。
[機能]クリア表示して終了
   [処理]クリアを表示する
   [処理]プログラムを終了する


次に、「処理」と「状態」の接続関係を調査します。
「処理」は起動されると一個の機能を実行するために、
「処理」から「処理」へと連鎖的に進んでいきます。
そして機能の実行が終了すると「状態」を変更して一単位が終了します。

今回は、「状態」が定義されていないため、この手順でやることはありません。


次に、「要素」の持つ属性を調査します。
「要素」は基本的に次のような属性調査を行います。
対象の「要素」が複数の属性に合致するなら、全て複合させます。

属性解説
単体単体の情報です。「この要素」の情報を一個保有しています。
保有する情報の値は一種類とは限りません。
(例えば、「RPGのキャラ」の単体データからは、「HP」「MP」「攻撃力」「守備力」
といった複数の情報を取得できるといった感じです)
複数「単体」属性の要素の集合体です。
同じ種類の「要素」が複数存在し得るとき、それを集めたものに付けられます。
実際の存在数が0個や1個であっても、複数になり得るなら複数データ分類は使用できます。
これからは単体データを取り出すことができます。
(例えば、「RPGの主人公パーティー」の複数データからは、
パーティーメンバーの「RPGのキャラ」単体データを取り出すことができます)
参照他の「要素」と情報を共有するものです。
「複数」属性の要素の中の一部を選択した場合など、
「他に存在する要素に付ける別名」のような存在です。
被参照「この要素」に対する参照属性の要素が存在する場合に対として付けられます。
要素への操作は参照属性の要素から行われる可能性もあるので、
被参照が付与されている要素の操作にも注意します。
作成「この要素」を作成することがある「処理」との関係として設定されます。
削除「この要素」を削除することがある「処理」との関係として設定されます。
固定各「処理」を実行する間、「この要素」の内容が変化しない場合に付けられます。
この属性は「この要素と接点のある処理」ごとに別々に設定されます。
入力各「処理」を実行する間、「この要素」の内容を参照する場合に付けられます。
この属性は「この要素と接点のある処理」ごとに別々に設定されます。
出力各「処理」を実行する間、「この要素」の内容を変更する場合に付けられます。
この属性は「この要素と接点のある処理」ごとに別々に設定されます。
瞬間この要素が、不意に変化する可能性がある場合に付けられます。
この属性を持つ要素は参照する度に違う値を返す可能性があります。
「マウスポインタの位置」「現在時間」など、時間経過やプログラム外の操作などに伴い、
変化し得るものに付けられます。
並行この要素への変更が完了する前に、参照する可能性がある場合に付けられます。
この属性を持つ要素は、必要なデータが蓄積されたことを確認してから参照しなければいけません。
(例えば、動画などのストリーミング再生では、データを全て読み込む前に再生を開始するため、
再生対象のデータにはこの属性が付きます)
仮想複数の種類の要素を束ねて一種類の要素に見せかけている場合に付けられます。
要素を扱う側は個々の要素の詳細を知らなくても、仮想属性の要素として、個々の要素を扱うことができます。
(例えば、Windowsのソフトは一般的にウィンドウ右上の×ボタンで終了できますが、
これは「Windowsのウィンドウ」という仮想属性に定義された「右上の×ボタンは終了」として扱うことができます。
その結果、「個々のソフトのウィンドウ」という「Windowsのウィンドウ」に束ねられた
個々の要素(ウィンドウ)の操作を知らなくても、ソフト終了できるというわけです)

今回の場合、以下のようになります。
問題は複数問あるので、「ソース上に直接書き込んだ問題」は複数属性です。
[要素]ソース上に直接書き込んだ問題
   [属性]複数:問題(未定義)
         入力 固定:[機能]乱数を使って選択し表示します→[処理]問題を選択する
[要素]選択された問題
   [属性]参照 単体:問題(未定義)
         作成:[機能]乱数を使って選択し表示します→[処理]問題を選択する
         入力 固定:[機能]乱数を使って選択し表示します→[処理]選択された問題を表示する
         入力 固定:[機能]成否を判定します→[処理]結果を判定し、分岐する
[要素]入力された答え
   [属性]単体:プレイヤーの回答
         出力:[機能]その問題の答えをプレイヤーはキーボードから入力し→プレイヤーから答えが入力されるのを待つ
         入力 固定:[機能]成否を判定します→[処理]結果を判定し、分岐する

ここで、未定義の「問題」ファクターが見つかったので、これを追加します。
[要素]問題
   [属性]単体:問題文 正解文
         被参照:選択された問題


ここまでの内容を整理すると、以下のようになります。
   
[要素]ソース上に直接書き込んだ問題
   [属性]複数:問題
         入力 固定:[機能]乱数を使って選択し表示します→[処理]問題を選択する
[要素]選択された問題
   [属性]参照 単体:問題(未定義)
         作成:[機能]乱数を使って選択し表示します→[処理]問題を選択する
         入力 固定:[機能]乱数を使って選択し表示します→[処理]選択された問題を表示する
         入力 固定:[機能]成否を判定します→[処理]結果を判定し、分岐する
[要素]入力された答え
   [属性]単体:プレイヤーの回答
         出力:[機能]その問題の答えをプレイヤーはキーボードから入力し→プレイヤーから答えが入力されるのを待つ
         入力 固定:[機能]成否を判定します→[処理]結果を判定し、分岐する
[要素]問題
   [属性]単体:問題文 正解文
         被参照:選択された問題
[機能]乱数を使って選択し表示します
   [処理]乱数を発生させる
   [処理]問題を選択する
   [処理]選択された問題を表示する
[機能]その問題の答えをプレイヤーはキーボードから入力し
   [処理]プレイヤーから答えが入力されるのを待つ:「[要素]入力された答え」へ結果を出力
[機能]成否を判定します
   [処理]結果を判定して分岐:正解なら「[機能]クリア表示して終了」へ進む
                         間違いなら「[機能]その問題の答えをプレイヤーはキーボードから入力し」へ進む
[機能]クリア表示して終了
   [処理]クリアを表示する
   [処理]プログラムを終了する

このリストだけで関係性を把握できる場合はそれで構いませんが、
これだけでは良くわからない場合、処理と処理の関係、機能と機能の関係といったように、
一種類づつに注目して線で結ぶなどして図を起こしてみると、分かりやすくなる(と思います)。

一応、図も作ってみました(ペイントで平描きしたのでへろへろですが・・・(汗
(10/02/05追記) PowerPoint使って図を作りなおしました
各要素の関係図

個々の「処理」は所属する「機能」でひとまとめにしちゃってますが、
この辺の割り切りというか、そういうのも結構重要です。
この程度の簡単な仕様でさえこれだけの要素や機能、処理が出てくるのですから、
大規模なプログラムともなると形成される関係図は大変な大きさになってしまいます。
予想ではありますが、5万行級のプログラムであれば、
この粒度でも模造紙を使っても書けないような大きさになってしまうでしょう。

そのため、機能と要素の関係がまとまっている部分を使って、グループ化していくことが必要になります。
今回は「機能」までのグループ化で十分ですが、「機能」を集めたものをグループ化し、
それをさらにグループ化し・・・と、ある程度の数に絞れるまで絞っていきます。
この辺が「機能」の分解のところで触れた「階層構造を持たせる」ということになります。


さて、次回は今回作成した図を元に、実際のプログラムを作成していく予定です。


[前へ]   [目次へ]   [次へ]

プログラミング講座 総合目次

最終更新 2010/02/05