Javaによるプログラミング入門13
値を返すメソッドの例

 今日は、懸案だった「大魔王とヒーローが対決するゲーム」の改良です。そこで、前回紹介した「値を返すメソッド」を使います。
 前回は「値を返すメソッド」を勉強しました。値を返す(戻す)ということは、普通、Javaでは「報告」に使われます。メソッドというものはプログラムのどこかで使われる(これを、業界では「呼び出される」と言うのでした)のですが、その際に、使っている場所(業界用語では「呼び出しているメソッド」となります。メソッドを使うのもメソッドであるからです。少し、ややこしいですね。)に、値を「返す」からです。なんだかややこしいですね。でも、難しいことではないのです。
 もう一度、前回の例を見てみましょう。

//Ex3.java
class X
{
    int method(){
        System.out.println("こんにちは。私はXのオブジェクトです。");
        return 1;
    }
}

class Ex3
{
    public static void main(String[] args){
        int d;  //整数変数dの宣言(「整数dを使うよ」という意味)
        X x = new X();
        d = x.method();//考え込むと不思議な文ですが、次のような意味になります。
                //まず、Xのmethodの中身が実行される。
               
//そして、このメソッドから返された値1がdに代入される。
        System.out.println("methodから返された値=" + d);  //dの値の出力
    }
}

 ここで、methodはもちろんメソッドですが、実は、mainもメソッドなのでした。(mainは特別なメソッドです。)それで、上のプログラムは「メソッドmainがメソッドmethodを呼び出している」ということができます。呼び出されたmethodは呼び出したmainに値1を返しています。(「返す」と言っても、もともとあったものを返還するという意味ではないのでした。上のプログラムではdに1が代入されることを「返す」というのです。)
 ここで、もし、dに1が代入されていなければ、(上の例では単純すぎますが)なんらかの原因でエラーがおこったと判断できます。このような返り値は成功・不成功の報告(エラーチェック)になるのです。

 上の例のmethodはいつも1を返すように作ってありますが、いろいろな状況に応じて返す値を変える方が普通です。その場合は、エラーチェックというより、プログラムの進行の細かいチェックになるわけです。つまり、呼び出した側で返り値を見て次の作業をすすめるようにすることができるのです。
 入門9では「大魔王とヒーローが対決するゲーム」(Game.java)をつくりましたが、Game.javaは、対決が一度きりなので、ゲームの体裁になっていませんでした。これを「決着がつくまで対決を繰り返すプログラム」に改造することは今までの知識だけでできるはずです。元気のある人は自分でも考えてみてください。 そこまで元気はないという人も、下のサンプルを見る前に、入門9のプログラムの流れをもう一度見ておいてください。


 まず、ヒントを書き、次に答を書きますが、人によっては答を先に見てから、その後にヒントを読んだ方がわかりやすいかもしれません。読み方は各自工夫してください。

 さて、私なら次のように考えます。まず、なるべく入門9のコードを使って改造するのが得策ですから、対決には、Taiketu_basyoのメソッドtaiketuをうまく使いたいと思います。(あとで少し修正します。)これをループの中に入れて

    while(決着がつかない間){
    taiketu();
    }

や、

   while(true){
    taiketu();
    決着がついたかどうか調べ、ついていればループをぬける
    }

とするのがよいでしょう。(入門11を参照)
 「決着」は大魔王かヒーローのパワーが負になった場合です。これは、少し複雑なので、下の方の無限ループの形を採用しましょうか。(もちろんそれほど本質的な違いはありませんが。)このコードを使って、「何度も繰り返し対決するメソッド」を書くのです。それはkurikaesi_taiketuとし、クラスTaiketu_basyoに加えることにします。
 しかし、決着がついたかどうかを判断するためには大魔王やヒーローのパワーを知る必要があります。パワーはどちらのクラスでもpowerとよばれているものですが、プライベートにしてあるので、Taiketu_basyoのkurikaesi_taiketuの中で直接使うことはできません。(そのように使えないようにプライベートにしたのです。なぜ、そんな不便なことをしたかは、入門6を見てください。)
 そこで、クラス「ヒーロー」にも「大魔王」にも、powerを報告するだけのメソッドgetPowerを付け足します。これはpowerを返り値にするだけのメソッドなので、クラスの定義の中に書いてしまいます。すると、例えば、クラス「ヒーロー」は、次のようになります。

//ヒーローつまり英雄ですね
class Hero
{
    //ヒーローのパワー
    private int power;
    //ヒーローのコンストラクタ、ヒーローのパワーをはじめ100とした
    Hero(){ power = 100; }
    //「powerを返す」メソッド
    int getPower(){ return power; }
   //「ヒーローが攻撃する」メソッド。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("ヒーローは倒れました。");
        }
    }
}

こうすると、例えば、youというHeroオブジェクトを参照する変数があった場合、

    int temp;
    temp = you.getPower();

と書けば、tempにそのときのyouのパワーが代入されるのです。大魔王にも同様のメソッドを付け足せば、kurikaesi_taiketu()の定義は次のようになるでしょう。(youがHeroで、buがMaoです。)

    void kurikaesi_taiketu() throws IOException{
        int temp;
        while(true){
            taiketu();
            temp = you.getPower();
            if(temp < 0){
                System.out.println("\n大魔王の前に正義のヒーローは敗れました。");
                break;
            }
            temp = bu.getPower();
            if(temp < 0){
                System.out.println("\n正義のヒーローの前に大魔王は敗れました。");
                break;
            }
        }
    }

ここで、「throws IOException」をメソッド名の横に付けるは、内部でIOExceptionを投げるかもしれないtaiketuを使っているため、その処理方法を書く必要があるからです。もちろん、「throws IOException」は、「何もせずに例外を外に投げろ」という意味ですが、IOExceptionはこのように何らかの処理方法書く必要があるのでした。(入門9の最後を参照してください。)
 上のメソッドは、また、you.getPower()などがそのまま返り値に変わるということを考えれば、一時変数tempを使わずに

    void kurikaesi_taiketu() throws IOException{
        while(true){
            taiketu();
            if(you.getPower() < 0){
                System.out.println( "\n大魔王の前に正義のヒーローは敗れました。");
                break;
            }
            if(bu.getPower() < 0){
                System.out.println( "\n正義のヒーローの前に大魔王は敗れました。");
                break;
            }
        }
    }

としても同じです。こちらの方がすっきりしていますよね。

 それから、taiketu メソッドも少し変えます。攻撃はヒーローと大魔王が交互に行いますが、taiketuではいつもヒーローが先です。ここで、もしヒーローが先に大魔王を倒してしまえば(つまり大魔王のパワーが負になってしまえば)、次の大魔王の攻撃は行われないようにしなければなりません。そこで、taiketu内のヒーローの攻撃の後で、bu.getPower()を呼び出し、もし大魔王のパワーが負なら残りの部分(つまり大魔王の攻撃の部分)を実行する前に、taiketuを終了してしまうように改造します。メソッドを終了させる命令は「return」(taiketuは値を返さないメソッドなので「return;」ですね)です。これを利用することにします。
 また、ヒーローや大魔王が攻撃した際にパワーを使いすぎて死んでしまうこともあるでしょうから、これについてもチェックをいれるよう改造します。(以上の詳細は下のサンプルをみてください。returnはbreakによくにていることがわかると思います。ループなどからぬけるのがbreakでメソッドからぬけるのがreturnなのです。)

 あといくつか細かいこともあります。まず、入門9のGame.cでは、ユーザに攻撃をうながすときに

System.out.print("攻撃に使うパワー(100以下の数値)を入力してください。>> ");

としていました(printは「改行なしの出力」という意味でした)が、今度は、攻撃のたびにエネルギーが減っていくので、これを

System.out.print("攻撃に使うパワー(" + you.getPower() + "以下の数値)を入力してください。>> ");

とします。ちょっと見にくいですが、まず、「攻撃に使うパワー(」と「you.getPower()(返り値)」と「以下の数値)を入力してください。>> 」を文字列としてつないで出力するのです。これによって、現在のヒーローのエネルギーが表示されるようになります。
 それから、ヒーローも大魔王もkougeki_suruメソッドやkougeki_sareruメソッドでパワーがつきると何か言ってから倒れるのですが、さらにその後に

System.out.println("ヒーローは倒れました。");

などという文があります。これは単純なプログラムを少しでもその時点でゲームらしくするために付け加えた文でしたが、実際には、自分で自分のことを「倒れました」などとコメントするのはおかしいですよね。プログラム的にもこの部分は蛇足なので削ります。そのかわり、「倒れた」というコメントはkurikaesi_taiketuの中に入れました。
 また、プログラムの構造とは関係ないのですが、kougeki_suruなどで自分のパワーをユーザに報告するのがあまりに唐突なので、

System.out.println("現在のパワーは" + power + "だ。");

System.out.println("(現在のパワーは" + power + "になった。)");

と変えることにしました。

 以上をまとめるとGame.javaは、次のように改良されます。

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

//ヒーローつまり英雄ですね
class Hero
{
    //ヒーローのパワー
    private int power;
    //ヒーローのコンストラクタ、ヒーローのパワーをはじめ100とした
    Hero(){ power = 100; }
    //「powerを返す」メソッド
    int getPower(){ return power; }
   //「ヒーローが攻撃する」メソッド。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("もうおしまいだ!!!がくっ。");
        }
    }
    //「ヒーローが攻撃される」メソッド。nは攻撃されて減らされるパワーの量。
    void kougeki_sareru(int n){
        System.out.println("くそっ。悪党の攻撃も当たることがあるのか。");
        power -= n;  //攻撃されてパワーが減る。
        //以下の仕組みはkougeki_suru()とほぼ同じ。
        if(power >= 0){
            System.out.println( "(現在のパワーは" + power + "になった。)");
        }
        else{
             System.out.println("やられた。がくっ。");
        }
    }
}

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

        //もしパワーがまだ0以上ならよし、もし、負になったら、使いすぎで負けとした。
        //しかし、大魔王のパワーは秘密なので書かない。
        if(power < 0){
            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("やられた。がくっ。");
        }
    }
}

//対決場所のクラス(ヒーローや大魔王が「もの」なら、対決場所も「もの」ですね。)
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に代入された値、この値だけヒーローのパワーは減る
        //taiketu()の改造した場所
        if(you.getPower() < 0){ //攻撃のしすぎで、ヒーローのパワーが負になれば、、、
            return;              //returnはそのメソッドの実行を終了させる
        }
        if(bu_no_basho == iti){  //もしbu_no_bashoとitiが一致したら、
                           //つまり、ねらったところに大魔王がいたら、という意味
            bu.kougeki_sareru(kougeki * 2);  //攻撃を受けます。このとき大魔王は
                                       //ヒーローが使ったパワーの2倍を消耗します。
                                       //kougeki * 2とはkougekiの2倍という意味です。
            //taiketu()の改造した場所
            if(bu.getPower() < 0){  //攻撃されて、大魔王のパワーが負になれば、、、
                return;     //returnはそのメソッドの実行を終了させる
            }

        }
        else{
            System.out.println("あなたの攻撃ははずれたようです。");  //はずれたら、何も起こらない
        }
        System.out.println();                   //改行という意味
        System.out.println("大魔王の攻撃です。");
         System.out.println("(エンターキーを押してください。)" );
        br.readLine();
        iti = (int)(Math.random() * 5) + 1;  //大魔王の攻撃の位置は乱数(ここでは1〜5までのうちのどれか)で決まる
        kougeki = (int)(Math.random() * 100) + 1;  //大魔王の攻撃の量も乱数(1〜100までのうちのどれか)で決まる
        bu.kougeki_suru(kougeki);  //大魔王Buの攻撃(大魔王のパワーが減る)
        //taiketu()の改造した場所
        if(bu.getPower() < 0){  //攻撃のしすぎで大魔王のパワーが負になれば、、、
            return;    //returnはそのメソッドの実行を終了させる
        }
        if(you_no_basho == iti){  //当たったら
            you.kougeki_sareru(kougeki * 2);  //攻撃された
        }
        else{  //はずれたら
            System.out.println("大魔王ブーの攻撃ははずれたようです。");
        }
    }
    void kurikaesi_taiketu() throws IOException{
        while(true){
            taiketu();
            if(you.getPower() < 0){
                System.out.println( "\n大魔王の前に正義のヒーローは敗れました。");
                break;
            }
            if(bu.getPower() < 0){
                System.out.println( "\n正義のヒーローの前に大魔王は敗れました。");
                break;
            }
        }
    }
}

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

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

Fig.1 Game2の実行

 まだまだ、不満はあります。たとえば、パワー切れで攻撃しても「どか〜ん」といってしまうところや、大魔王の攻撃が完全にランダムだというようなところです。でも、疲れましたね。今日はこの辺にしましょう。ゲーム作りがこの講座の目的ではないので(といっていいわけをしているような、、、)、このプログラムをおもしろくする方向の改造はみなさんにお任せして、以後はJavaの技法を学ぶ方向に進むことにします。


 この「講座」は全くの初心者を想定して書かれていますが、この辺は初心者には厳しいかもしれません。そうでもない、という人は特別才能のある人でしょう。難しいと思っている人ががっかりすることはありません。そういう人は、上のプログラムをとにかく実行してみることと、ソースをプリントアウトして部分的にでも理解するようときどき眺めてみることをおすすめします。1週間くらい眺めていると(真剣に考えるのではなく、ただ、眺めているだけでよいと思います)そのうちわかってくると思います。まあ、そういうことです。^^)
目次のページ
前のページ 後のページ