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

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

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

class Hero
{
    ヒーローの定義
}

class Daimao
{
    大魔王の定義
}

class Kaiju
{
    怪獣の定義
}

・・・

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

class Syouhin
{
    商品の定義
}

class Meibo
{
    顧客名簿の定義
}

class ZaikoKiroku
{
    在庫記録の定義
}

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


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

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

//Game.java
import java.io.*;   //読み込み処理のために必要

//ヒーローつまり英雄ですね
class Hero
{
    //ヒーローのパワー
    private int power;
    //ヒーローのコンストラクタ、ヒーローのパワーをはじめ100とした
    Hero(){ power = 100; }
   //「ヒーローが攻撃する」メソッド。nは攻撃に使うパワー。
    void kougeki_suru(int n){
        System.out.println("悪党め。正義の攻撃を受けてみよ。");
        System.out.println("どか〜ん!!!");
        power -= n;  //攻撃したのでパワーを減らします。
        //もちろん本当にパワーをどこかにやったのではなく、単に数値を減らすだけでした。

        //もしパワーがまだ0以上ならよし、もし、負になったら、使いすぎで負けとした。
        //以下のifとelseはそういう意味です。
        if(power >= 0){
            System.out.println("現在のパワーは" + power + "だ。");
        }
        else{
            System.out.println("しまった!パワーを使いすぎた。");
            System.out.println("もうおしまいだ!!!がくっ。");
            System.out.println("ヒーローは倒れました。");
        }
    }
    //「ヒーローが攻撃される」メソッド。nは攻撃されて減らされるパワーの量。
    void kougeki_sareru(int n){
        System.out.println("くそっ。悪党の攻撃も当たることがあるのか。");
        power -= n;  //攻撃されてパワーが減る。
        //以下の仕組みはkougeki_suru()とほぼ同じ。
        if(power >= 0){
             System.out.println("現在のパワーは" + power + "だ。");
        }
        else{
             System.out.println("やられた。がくっ。");
             System.out.println("ヒーローは倒れました。");
        }
    }
}

//大魔王。実はヒーローとほとんど同じ構造!
class Daimao
{
    //大魔王のパワー
    private int power;
    //大魔王のコンストラクタ、大魔王のはじめのパワーは100とした
    Daimao(){ power = 100; }
    //「大魔王が攻撃する」メソッド。nは攻撃に使うパワー
    void kougeki_suru(int n){
        System.out.println("大魔王様の一撃をうけてみよ。");
        System.out.println("どか〜ん。");
        power -= n;  //攻撃したのでパワーを減らします。
        //もちろん本当にパワーをどこかにやったのではなく、単に数値を減らすだけでした。

        //もしパワーがまだ0以上ならよし、もし、負になったら、使いすぎで負けとした。
        //しかし、大魔王のパワーは秘密なので書かない。
        if(power < 0){
            System.out.println("しまった!!!パワーを使いすぎた。");
            System.out.println("む、む、む。無念だ。がくっ。");
            System.out.println("大魔王は倒れました。");
        }
    }
    //「大魔王が攻撃される」メソッド。nは攻撃されて減らされるパワーの量。
    void kougeki_sareru(int n){
        System.out.println("くそっ。正義の味方の攻撃も当たることがあるのか。");
        power -= n;  //攻撃されてパワーが減る。
        //以下の仕組みはkougeki_suru()とほぼ同じ。大魔王のパワーは秘密なので書かない。
        if(power < 0){
            System.out.println("やられた。がくっ。");
            System.out.println("大魔王は倒れました。");
        }
    }
}

//対決場所のクラス(ヒーローや大魔王が「もの」なら、対決場所も「もの」ですね。)
class Taiketu_basyo
{
    private Daimao bu;   //対決場所にいる大魔王Bu!
    private Hero you;    //対決場所にいるヒーローYou!
    private int bu_no_basho;   //大魔王のいる場所(1〜5の数値)、これは後で決まる
    private int you_no_basho;  //ヒーローのいる場所(1〜5の数値)、これは後でユーザが決める
    //対決場所のコンストラクタ
    Taiketu_basyo() throws IOException {
        bu = new Daimao();
        you = new Hero();
        bu_no_basho = (int)(Math.random() * 5) + 1;  //1〜5の中のでたらめな数(次回に説明)
        System.out.println("あなたと大魔王ブーとの決戦です。\n");
        System.out.println("大魔王ブーは座標1〜5のどこかに潜んでいます。");
        System.out.println("あなたもどこかに身を潜めてください。");
        System.out.println("身を潜める座標(1〜5の数値)を入力してください。");
        BufferedReader br =
            new BufferedReader(new InputStreamReader(System.in));
        String str = br.readLine();             //ユーザから入力を文字列として受け取る
        you_no_basho = Integer.parseInt(str);    //その文字列を整数に変換してyou_no_bashoに格納
    }
    //「ヒーローと大魔王が対決する」メソッド
    void taiketu() throws IOException {
        String str;     //ユーザから文字列を受け取るいれもの
        int iti, kougeki;  //一時的に必要な「位置」と「攻撃量」の変数(いれもの)
        System.out.println("さあ、あなたの攻撃です。");
        System.out.print("攻撃の位置(1〜5の数値)を入力してください。>> ");
        BufferedReader br =
            new BufferedReader(new InputStreamReader(System.in));
        str = br.readLine();             //ユーザから入力を文字列として受け取る
        iti = Integer.parseInt(str);    //その文字列を整数に変換してitiに格納
        System.out.print("攻撃に使うパワー(100以下の数値)を入力してください。>> ");
        str = br.readLine();             //ユーザから入力を文字列として受け取る
        kougeki = Integer.parseInt(str);     //その文字列を整数に変換してkougekiに格納
        System.out.println();                   //改行という意味
        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{
            System.out.println("あなたの攻撃ははずれたようです。");  //はずれたら、何も起こらない
        }
        System.out.println();                   //改行という意味
        System.out.println("大魔王の攻撃です。");
         System.out.println("(エンターキーを押してください。)" );
        br.readLine();      //1時ストップ(本文参照)
        iti = (int)(Math.random() * 5) + 1;  //大魔王の攻撃の位置は乱数(ここでは1〜5までのうちのどれか)で決まる
        kougeki = (int)(Math.random() * 100) + 1;  //大魔王の攻撃の量も乱数(1〜100までのうちのどれか)で決まる
        bu.kougeki_suru(kougeki);  //大魔王Buの攻撃(大魔王のパワーが減る)
        if(you_no_basho == iti){  //当たったら
            you.kougeki_sareru(kougeki * 2);  //攻撃された
        }
        else{  //はずれたら
            System.out.println("大魔王ブーの攻撃ははずれたようです。");
        }
    }
}

class Game
{
    public static void main(String[] args) throws IOException {
        Taiketu_basyo dokoka = new Taiketu_basyo();  //対決場所dokokaの生成
                             //ここでコンストラクタが働き、ヒーローと大魔王の位置が決められる。

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

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

Fig.1 Gameの実行

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

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

 いくつか新しいことがあります。
 まず、クラスTaiketu_basyoにある(int)(Math.random() * 5)などです。これについては次回に説明しますが、簡単にいうと乱数というでたらめな数字を発生させるためにあるものです。例えば、(int)(Math.random() * 5)は0〜4までの中で、(int)(Math.random() * 100)は0〜99までの中で、でたらめに選んだひとつの数という意味です。このような乱数が必要となるのはコンピュータに数字を選んでもらわないと(つまり乱数を発生させてもらわないと)魔王の位置やパワーがいつも同じになってしまうしかないからです。
 また、はじめてでてきましたが、

    System.out.println();

は、単に「改行せよ」という意味です。
 それから、

    br.readLine();

の意味はわかるでしょうか。これは「読み込み」の命令ですね。readLineを実行すると、「エンターキーが押されるまで入力待ち状態になる」ので、それを利用して、「プログラムの進行を一時ストップし、エンターキー(リターンキー)で進行を再開する」ようにしたのです。ただし、ここで読み込んだ文字列を利用することはないので、「str = br.readLine();」のように、読み込んだ文字列をどこかに格納することはしないのです。

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

 なお、Taiketu_basyoのコンストラクタとtaiketuメソッドの中では、キーボードからの読み込みをしています。(実は、System.in.read(); も読み込みの1種です。)そのような場合、IOExceptionという例外が発生するかも知れず、その処理を書いておかなければならないのでした(Java入門3を参照)。これまでの例では、mainの中でのみ読み込みを行っていましたが、今回は、Taiketu_basyoというクラスのコンストラクタとメソッドの中で行っているのです。そのため、これらの中で例外が発生した場合の指示を書いておかなければなりません。
 そこで、今までのmainの場合と同じく、「例外が発生したら外に投げ出せ」という意味で、「throws IOException」をコンストラクタとtaiketuメソッドの横に書きました。それでよいのです。また、これらをクラスGameのmainの中で使うので、mainの横にも同じものを付けてあります。


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