C++によるプログラミング入門9
クラス間の関係

 こんにちは。みなさん、いかがおすごしでしょうか?
 最近、更新のペースが落ちてますね。もし、待っている方がいましたら、感激ですが、すみません。
 さて、前回まで、クラスの書き方を説明してきました。しかし、どうして、クラスが有用なのかまだピンと来ないかもしれません。実際、「クラスがあってよかった」というプログラムはまだ書いていないのです。
 (ずいぶん前にも書きましたが、)一般的に言うと「クラスがあってよかった」というプログラムは、大規模なものになると思います。というのは、クラスは「プログラムの部品」と考えられるからです。「個々の部品を作って組み上げる」という方法は、大きなものを作るときにこそ向いているのです。
 残念ながら、そのように大きなプログラムをここでお見せすることはできません。そこで、大体の話をしてから、「それらしい例」に移ろうと思います。

 例えばゲームを考えてみましょうか。「ヒーロー」が「怪獣」や「魔王」と戦うようなゲームだとします。このようなプログラムでは、「登場人物」のほとんどすべてがクラスとして定義できるのです。プログラムは

class Hero
{
    ヒーローの定義
};

class Daimao
{
    大魔王の定義
};

class Kaiju
{
    怪獣の定義
};

・・・

のように、「登場人物」を定義し、それらを組み合わせて作ることができるはずです。これは、かなり、わかりやすいプログラミング手法ではないでしょうか。
 あるいは、業務用ソフトを考えるなら、「商品」や「名簿」や「在庫記録」が「登場人物」になるかもしれません。それなら、プログラムは

class Syouhin
{
    商品の定義
};

class Meibo
{
    顧客名簿の定義
};

class ZaikoKiroku
{
    在庫記録の定義
};

のように書くことができるはずです。(実際のプログラムでは、かなりの数のクラスが定義され、それらの関係(相互作用)が記述されることになると思います。)


 今回は、例として、ゲーム「大魔王とヒーローの対決」を作りましょう。(実に単純なゲームですのであまり期待しないようにしてください。)単純なゲームですが、それでもいくつかの準備がいります。しかし、あまり準備、準備では飽きてしまうと思うので、まず、とてつもなく単純なゲームの原形をつくり、それを少しずつ発展させていくという手法にします。その点は了解しておいてください。

 ゲームは次のようなものにしましょう。大魔王とヒーローが登場します。大魔王はどこかに隠れています。この隠れ場所は1〜5までの数字(座標)で表すことにします。ヒーローもどこかに隠れます。これも同じく1〜5までの座標のどこかにします。(話を単純にするため、大魔王とヒーローの座標が重なっても何も起こらないことにします。)大魔王もヒーローもはじめに100のパワーをもっていますが、このパワーの一部を使って相手に攻撃をします。攻撃は相手の座標を予測して、その場所にパワーをとばすことにします。もし、本当にその場所に相手がいれば相手は受けたパワーの2倍のダメージを受け、もし、その場所に相手がいなければただのパワーのむだ使いに終わるとします。このような攻撃を相互に繰り返し、先にパワーがなくなった方が負けとします。(ただし、今回はお互い1回しか攻撃できません。そうするとほとんどゲームにならなくて、何がなんだかわからないのですが、、。そのうちもう少しましになります。)また、大魔王はコンピュータが、ヒーローはユーザが操作するようにします。
 以上のような計画のもとで、下のようなプログラムをつくりました。ざっと見てください。(こういうのもいい勉強になると思います。)いくつか知らないものがあると思うのですが、全体の流れがつかめればよいと思います。少し長いですが細部にとらわれずにがんばってみてください。

//Game.cpp
#include <iostream>
#include <cstdlib>  //乱数のために必要(説明は次回)
#include <ctime>    //乱数のために必要(説明は次回)
using namespace std;

//ヒーローつまり英雄ですね
class Hero
{
    int power;                    //ヒーローのパワー
public:
    Hero() : power(100){}         //ヒーローのコンストラクタ、ヒーローのパワーをはじめ100とした
    void kougeki_suru(int n);     //「ヒーローが攻撃する」関数
    void kougeki_sareru(int n);   //「ヒーローが攻撃される」関数
};

//「ヒーローが攻撃する」関数の定義、nは攻撃に使うパワー
void Hero::kougeki_suru(int n)
{
    cout << "悪党め。正義の攻撃を受けてみよ。" << endl;
    cout << "どか〜ん!!!" << endl;
    power -= n;  //攻撃したのでパワーを減らします。
    //もちろん本当にパワーをどこかにやったのではなく、単に数値を減らすだけでした。

    //もしパワーがまだ0以上ならよし、もし、負になったら、使いすぎで負けとした。
    //以下のifとelseはそういう意味です。
    if(power >= 0){
        cout << "現在のパワーは" << power << "だ。" << endl;
    }
    else{
        cout << "しまった!パワーを使いすぎた。" << endl;
        cout << "もうおしまいだ!!!がくっ。" << endl;
        cout << "ヒーローは倒れました。" << endl;
    }
}

//「ヒーローが攻撃される」関数の定義、nは攻撃されて減らされるパワーの量
void Hero::kougeki_sareru(int n)
{
    cout << "くそっ。悪党の攻撃も当たることがあるのか。" << endl;
    power -= n;  //攻撃されてパワーが減る。
    //以下の仕組みはkougeki_suru()とほぼ同じ。
    if(power >= 0){
        cout << "現在のパワーは" << power << "だ。" <<endl;
    }
    else{
        cout << "やられた。がくっ。" << endl;
        cout << "ヒーローは倒れました。" << endl;
    }
}

//大魔王。実はヒーローとほとんど同じ構造です
class Daimao
{
    int power;                    //大魔王のパワー
public:
    Daimao() : power(100){}       //大魔王のコンストラクタ、大魔王のはじめのパワーは100とした
    void kougeki_suru(int n);     //「大魔王が攻撃する」関数
    void kougeki_sareru(int n);   //「大魔王が攻撃される」関数
};

//「大魔王が攻撃する」関数の定義、nは攻撃に使うパワー
void Daimao::kougeki_suru(int n)
{
    cout << "大魔王様の一撃をうけてみよ。" << endl;
    cout << "どか〜ん。" << endl;
    power -= n;  //攻撃したのでパワーを減らします。
    //もちろん本当にパワーをどこかにやったのではなく、単に数値を減らすだけでした。

    //もしパワーがまだ0以上ならよし、もし、負になったら、使いすぎで負けとした。
    //しかし、大魔王のパワーは秘密なので書かない。
    if(power < 0){
        cout << "しまった!!!パワーを使いすぎた。" << endl;
        cout << "む、む、む。無念だ。がくっ。" << endl;
        cout << "大魔王は倒れました。" << endl;
    }
}

//「大魔王が攻撃される」関数の定義。nは攻撃されて減らされるパワーの量
void Daimao::kougeki_sareru(int n)
{
    cout<< "くそっ。正義の味方の攻撃も当たることがあるのか。" <<endl;
    power -= n;  //攻撃されてパワーが減る。
    //以下の仕組みはkougeki_suru()とほぼ同じ。大魔王のパワーは秘密なので書かない。
    if(power < 0){
        cout << "やられた。がくっ。" << endl;
        cout << "大魔王は倒れました。" << endl;
    }
}

//対決場所のクラス(ヒーローや大魔王が「もの」なら、対決場所も「もの」ですね。)
class Taiketu_basyo
{
    Daimao bu;         //対決場所にいる大魔王bu!
    Hero you;          //対決場所にいるヒーローyou!
    int bu_no_basho;   //大魔王のいる場所(1〜5の数値)、これは後で決まる
    int you_no_basho;  //ヒーローのいる場所(1〜5の数値)、これは後でユーザが決める
public:
    Taiketu_basyo();   //対決場所のコンストラクタ、定義はクラス宣言の外で
    void taiketu();    //「ヒーローと大魔王が対決する」関数
};

//対決場所のコンストラクタの定義
Taiketu_basyo::Taiketu_basyo()
{
    srand( (unsigned)time( NULL ) ); //乱数の初期化=「乱数開始のおまじない」(次回に説明します)
    bu_no_basho = rand() % 5 + 1;    //rand()% 5は0〜4の中のでたらめな数(乱数)
    //したがってrand() % 5 + 1は1〜5のうちのどれか(次回に説明します)、これで大魔王の場所が決まった
    cout << "あなたと大魔王ブーとの決戦です。\n" << endl;
    cout << "大魔王ブーは座標1〜5のどこかに潜んでいます。" << endl;
    cout << "あなたもどこかに身を潜めてください。" << endl;
    cout << "身を潜める座標(1〜5の数値)を入力してください。" << endl;
    cin >> you_no_basho;             //ヒーローの場所を入力
}

void Taiketu_basyo::taiketu()   //「ヒーローと大魔王が対決する」関数の定義
{
    int iti, kougeki;           //一時的に必要な「位置」と「攻撃量」の変数(いれもの)
    cout << "さあ、あなたの攻撃です。" << endl;
    cout << "攻撃の位置(1〜5の数値)を入力してください。>> ";
    cin >> iti;                //攻撃する位置をitiに代入
    cout << "攻撃に使うパワー(100以下の数値)を入力してください。>> ";
    cin >> kougeki;            //攻撃に使うパワーをkougekiに代入
    cout << endl;
    you.kougeki_suru(kougeki); //ヒーローyouの攻撃
                               //攻撃量はkougekiに代入された値、この値だけヒーローのパワーは減る
    if(bu_no_basho == iti){    //もしbu_no_bashoとitiが一致したら(つまり、ねらったところに大魔王がいたら)
        bu.kougeki_sareru(kougeki * 2);  //攻撃を受けます。このとき大魔王は
                                         //ヒーローが使ったパワーの2倍を消耗します。
                                         //kougeki * 2とはkougekiの2倍という意味です。
    }
    else{
        cout << "あなたの攻撃ははずれたようです。" << endl;  //はずれたら、何も起こらない
    }
    cout << endl;
    cout << "大魔王の攻撃です。" << endl;
    cout << "(エンターキーを押してください。)" << endl;
    cin.sync();       //cinをフラッシュ(次回に説明します)
    cin.get();        //1時ストップ(次回に説明します)
    iti = rand() % 5 + 1;       //大魔王の攻撃の位置は乱数(ここでは1〜5までのうちのどれか)で決まる
    kougeki = rand() % 100 + 1; //大魔王の攻撃の量も乱数(1〜100までのうちのどれか)で決まる
    bu.kougeki_suru(kougeki);   //大魔王buの攻撃(大魔王のパワーが減る)
    if(you_no_basho == iti){              //当たったら
        you.kougeki_sareru(kougeki * 2);  //攻撃される
    }
    else{                                 //はずれたら
        cout << "大魔王ブーの攻撃ははずれたようです。" << endl;
    }
}

int main()
{
    Taiketu_basyo dokoka;  //対決場所Dokokaの生成
                           //ここでコンストラクタが働き、ヒーローと大魔王の位置が決められる。

    dokoka.taiketu();      //dokokaの対決
}

 あんまり長くていやになってしまった人、すみません。もう、しばらくは、こんなに長いのはありませんのでご安心を。(コメントを取ると少し短くはなるのですが、、、。)
 それでは、とりあえずコンパイルして、少し遊んでみてください。ヒーローと大魔王がお互い一度しか攻撃しないので、すぐ終わってしまって何をやっているのかわからないかもしれませんが、何度か実行してみればどんなゲームかわかると思います。

Fig.1 Game.exeの実行

 なお、あちこちにコメントをいれましたが、これらはあってもなくても同じです。ただ、もし、ブラウザの画面が小さくてコメント行が

//これは長い長いコメントで、、、、、、、
まだまだつづく、、、、
などとなっているのをそのまま写すとコンパイルの時にエラーとなります。これは、たとえば
//これは長い長いコメントで、、、、、、、
//まだまだつづく、、、、
としてください。

 Game.cppでは、新しいことも使っています。それはクラスTaiketu_basyoにあるsrand( (unsigned)time( NULL ) );やrandです。これらについては次回に詳しく説明しますが、簡単にいうと乱数というでたらめな数字を発生させるためにあるものです。(例えば、「srand() % 100」は0〜99までの中で、「srand() % 5」は0〜4までの中で、でたらめに選んだひとつの数という意味です。srand( (unsigned)time( NULL ) );はこのような乱数をはじめるための合図のようなものです。まあ、来週説明します。)このような乱数が必要となるのはコンピュータに数字を選んでもらわないと(つまり乱数を発生させてもらわないと)魔王の位置やパワーがいつも同じになってしまうしかないからです。
 また、

    cin.sync(); 
    cin.get();

の2行もまだ説明していませんよね。この説明も次回にしますが、とりあえず、この2行で「プログラムの進行を一時ストップし、エンターキー(リターンキー)で進行を再開する」と考えてください。

 乱数や上の2行のことはさて置き、大魔王やヒーローのクラスは理解できるのではないでしょうか。このふたつのクラスをつくり、その対決場所のクラスTaiketu_basyoをつくりました。当然、ヒーローと大魔王は対決場所Taiketu_basyoの中にいるのです。いままでクラスの中のメンバ変数はintとかstringといった「はじめから用意されている型のもの」でしたが、自分で作ったクラス(HeroやDaimao)の「もの」(オブジェクト)でもよいのです。
 どのように対決するかはtaiketu()に定義しました。taiketu()では、ヒーローと大魔王が順番に攻撃しあうようにしてあるのです(今回は、それぞれ1回ずつ)。おかげで、あとはmainの中で対決を開始すればよいだけなのです。コンパイルと実行ができ、このような関係がなんとなくでもわかれば、今日は合格です。
 細部はともかく、プログラム全体の流れは、つかみやすいのではないでしょうか。これで、クラスがプログラムの部品であることを、納得いただければと思います。


目次のページ
前のページ 次のページ