ねこあつめゲームを作成してみよう!(Java)

Java

Javaでオブジェクト指向や、ArrayListなどを学んだ人のための学習コンテンツ。
このお題を解くことで、クラス、インスタンス、ArrayListについて学習できる。
最後にはおまけとしてファイルの読み書きを行いデータのセーブを行う。

今回の制作環境はエクリプスなどのIDEは用いずにテキストエディタでのコーディングを前提としている。

実行例

まずは実行例

***猫集め***
1.集める 2.遊ぶ 3.終了>>2
まだ遊ぶ猫がいません。。。
1.集める 2.遊ぶ 3.終了>>1
白猫を見つけた!
この猫に名前をつけてください>>たま
たまが仲間に加わった!
1.集める 2.遊ぶ 3.終了>>1
黒猫を見つけた!
この猫に名前をつけてください>>ゴンズ
ゴンズが仲間に加わった!
1.集める 2.遊ぶ 3.終了>>1
茶トラ猫を見つけた!
この猫に名前をつけてください>>やま
やまが仲間に加わった!
1.集める 2.遊ぶ 3.終了>>2
0・・・たま[白](0)
1・・・ゴンズ[黒](0)
2・・・やま[茶トラ](0)
どの猫とあそびますか?>>1
ゴンズと遊んだ
...
ゴンズの親密度がアップした!
1.集める 2.遊ぶ 3.終了>>2
0・・・ゴンズ[黒](1)
1・・・たま[白](0)
2・・・やま[茶トラ](0)
どの猫とあそびますか?>>2
やまと遊んだ
...
やまの親密度がアップした!
1.集める 2.遊ぶ 3.終了>>2
0・・・ゴンズ[黒](1)
1・・・やま[茶トラ](1)
2・・・たま[白](0)
どの猫とあそびますか?>>1
やまと遊んだ
...
やまの親密度がアップした!
1.集める 2.遊ぶ 3.終了>>2
0・・・やま[茶トラ](2)
1・・・ゴンズ[黒](1)
2・・・たま[白](0)
どの猫とあそびますか?>>2
たまと遊んだ
...
たまの親密度がアップした!
1.集める 2.遊ぶ 3.終了>>2
0・・・やま[茶トラ](2)
1・・・ゴンズ[黒](1)
2・・・たま[白](1)
どの猫とあそびますか?>>1
ゴンズと遊んだ
...
ゴンズの親密度がアップした!
1.集める 2.遊ぶ 3.終了>>3
***結果***
やま[茶トラ](2)
ゴンズ[黒](2)
たま[白](1)
また遊んでね。おしまい

仕様

○猫は何匹でも集めることができる
○見つかる猫は[黒、白、茶トラ]の3種類からのランダム
○たま[白](1)という表記は
名前[猫の種類](親密度)
を表している
○一回遊ぶと親密度が1あがる
○一覧表示の際は親密度の高い猫から順に表示される

作成

では作成していこう。CatApp.javaファイルを作成し以下のように記述する。

import java.util.*;
public class CatApp{
  public static void main(String[] args){
  }
}

class Cat{
}

ポイント解説

今回登場する猫はそれぞれが名前と種類、親密度を持つ。
Javaでこういった事案を扱うときには一般クラスを作成するとよい。
今回はCatというクラスを作成した。
クラスを作る際には1つのファイルに1つのクラスが原則だが、こういった小規模のアプリであったら1枚にまとめてしまった方が楽だ。

一般クラスの作成(Catクラス)

それでは実際にCatクラスを作成していこう。
ここでまず考えることはどういったフィールドを作成するかだ。
フィールドというのは言い換えればパラメータのことで、その1匹の猫がもつ固有の情報のことだ。
今回猫ちゃんたちがもつ情報はなんだろうか?
そう、
名前
種類
親密度
の3つだ。
型を考えながら以下のようにフィールドを追記する。

class Cat{
  String name;
  String type;
  int intimacy=0;
}

親密度に関しては最初は0なので、ここで明示しておくとよいだろう。フィールド変数なので、この0は書かなくても初期値として0が代入されているが、明示しておいたほうがわかりやすい。

次にコンストラクタを考える。どういったコンストラクタがあったら便利かを考えればよい。今回の場合(名前、種類)を指定してインスタンスを生成できると便利だ。以下のように追記する。

class Cat{
  String name;
  String type;
  int intimacy=0;

  Cat(String name,String type){
    this.name=name;
    this.type=type;
  }
}

次にこのクラスにどういったメソッドがあったら便利かを考える。
まず、自分自身の情報を文字列情報として表せるメソッドがあると便利だろう。
なぜなら、処理の中で
やま[茶トラ](0)
という自分自身の情報を出力するところがたくさん出てくるからだ。
以下のように追記

class Cat{
  String name;
  String type;
  int intimacy=0;

  Cat(String name,String type){
    this.name=name;
    this.type=type;
  }
  
  String showStatus(){
    return String.format("%s[%s](%d)",this.name,this.type,this.intimacy);
  }
}

returnされているものに注目してもらいたい。
ここでは

return this.name+"["+this.type+"]"+"("+this.intimacy+")";

と文字列連結を用いてもよいが、書式を使えるString.format()を使っている。

メソッドをどこに記述するかを考える際、重要となるのが自分のフィールド(パラメータ)を操作する部分があったらそれはインスタンスメソッドとして作成するということだ。
今回は遊ぶことによって親密度があがるのでこの部分はインスタンスメソッドとして記述していこう。
以下のメソッドをshowStatus()の下に追記する

void play(){
    System.out.println(this.name+"と遊んだ");
    System.out.println("...");
    System.out.println(this.name+"の親密度がアップした!");
    this.intimacy++;
 }

これで、一旦Catクラスは完成だ。

CatAppクラスの作成

それでは、アプリケーションクラスにあたるCatAppクラスを作成していこう。

役者を揃える

まずは確実に必要になるであろう役者を揃えていく。
今回で言えば、猫の種類はランダムに決める必要があるのでRandomインスタンス、ユーザーからの入力を受け取るのでScannerインスタンスが必要となる。まずはこれをnewしよう。以下のようにCatAppクラスに記述

import java.util.*;
public class CatApp{
  public static void main(String[] args){
    Random rand=new Random();
    Scanner sc = new Scanner(System.in);
  }
}

続いて定数を定義していこう。今回[白,黒,茶トラ]という猫の種類は配列で扱っておくと便利そうだ。以下のように定数として記述する。

public static void main(String[] args){
    Random rand=new Random();
    Scanner sc = new Scanner(System.in);
    final String[] TYPES={"黒","白","茶トラ"};
}

そして、今回の主役である。Catインスタンスを格納するArrayListもこのタイミングで用意しておこう。

public static void main(String[] args){
    Random rand=new Random();
    Scanner sc = new Scanner(System.in);
    final String[] TYPES={"黒","白","茶トラ"};
    ArrayList<Cat> list = new ArrayList<>();
  }

今回は猫を何匹でも集められる仕様なので、この場合は要素数が決まっている配列よりもArrayListの方が便利だ。

準備ができたのでメインの処理を考えていこう。
実行例を見ると、
メニューの表示->選択->何かしら処理
というのが繰り返されているのがわかる。なのでforかwhileを使うことになる。
繰り返す回数がわかる場合はforそうでない場合はwhileを使うというのが基本だ。
今回は何度繰り返されるかわからない、なのでwhile文を使っていこう。以下のように追記する。

public static void main(String[] args){
    Random rand=new Random();
    Scanner sc = new Scanner(System.in);
    final String[] TYPES={"黒","白","茶トラ"};
    ArrayList<Cat> list = new ArrayList<>();
    System.out.println("***猫集め***");
    while(true){
    }
 }

いろいろなループの組み方があるが、while(true)で回して,breakやreturnで抜ける手法はわかりやすので初心者には特におすすめだ。続いて以下のように追記

public static void main(String[] args){
    Random rand=new Random();
    Scanner sc = new Scanner(System.in);
    final String[] TYPES={"黒","白","茶トラ"};
    ArrayList<Cat> list = new ArrayList<>();
    System.out.println("***猫集め***");
    while(true){
      System.out.print("1.集める 2.遊ぶ 3.終了>>");
      int select = sc.nextInt();
    }
  }

まずは、終了が選ばれたときの処理を記述していこう。

if(select == 3){
        System.out.println("***結果***");
        for(Cat c :list){
          System.out.println(c.showStatus());
        }
        System.out.println("また遊んでね。おしまい");
        return;
 }

このように特殊な場合にすぐにreturnして処理を抜けるというのは早期リターンと呼ばれていて、処理のネストが深くならない上、わかりやすいという特徴があるのでおすすめだ。

続いて以下のように記述。

while(true){
      System.out.print("1.集める 2.遊ぶ 3.終了>>");
      int select = sc.nextInt();
      if(select == 3){
        System.out.println("***結果***");
        for(Cat c :list){
          System.out.println(c.showStatus());
        }
        System.out.println("また遊んでね。おしまい");
        return;
      }
      if(select == 1){

      }else
      if(select == 2){
        
      }
    }

このように分岐の骨組みを先に書いてしまうのもよく行う手法だ。
今回、if~elseで組んでも同じだが、1を選んだのか2を選んだのかがわかりやすいいように上記のif ~ else if文を使った。

続いて1を選んだときの処理を以下のように記述

if(select == 1){
        String type=TYPES[rand.nextInt(TYPES.length)];
        System.out.printf("%s猫を見つけた!%n",type);
        System.out.print("この猫に名前をつけてください>>");
        String name=sc.next();
        Cat cat = new Cat(name,type);
        list.add(cat);
        System.out.println(cat.name+"が仲間に加わった");
 }

最初の行は,猫の種類の配列から一つを取り出している。ほぼイディオムとも言ってよい処理なので問題はないだろう。
そのあと、名前を受け取りCatインスタンスを生成し、リストに追加するというこのアプリの一番大切な部分を行っている。

続いて2を選択したときの処理を以下のように記述

if(select == 2){
        if(list.size() == 0){
          System.out.println("まだ遊ぶ猫がいません。。。");
          continue;
        }
}

ここでもまずは異常系の処理を書いていこう。
このように最初に異常系の処理を書いて抜けるようにするとネストが浅くなる。ここではreturnではなくcontinueだ。
続いて正常系の処理を以下のように記述

if(select == 2){
        if(list.size() == 0){
          System.out.println("まだ遊ぶ猫がいません。。。");
          continue;
        }
        for(int i=0;i<list.size();i++){
          System.out.printf("%d・・・%s%n",i,list.get(i).showStatus());
        }
        System.out.print("どの猫と遊びますか?>>");
        int no = sc.nextInt();
        list.get(no).play();
}

リストに入っている猫をgetで取り出して、そのインスタンスがplayメソッドを実行することで親密度があがるのであった。

仕様によると、今回は親密度で並び替えを行わなくてはならない。なので親密度が変わったまさにこのあとにlistのソート処理をいれる。ただ、この下に書くと2を選んだときの記述が長くなりすぎるので、この部分は以下のようにメソッドにしよう。以下の処理をメインメソッドの外に書く。

public static void main(String[] args){
~略~
}
static void sortCat(ArrayList<Cat> list){
    for(int i=0;i<list.size()-1;i++){
      for(int j=i+1;j<list.size();j++){
        if(list.get(i).intimacy < list.get(j).intimacy){
          Cat temp=list.get(i);
          list.set(i,list.get(j));
          list.set(j,temp);
        }
      }
    }
  }

オブジェクトが入ったリストのソート方法は色々あるが( 詳しくはこの記事参照)
今回は単純ソートのアルゴリズムを使ってリストをソートしている。

メソッドができたので以下の場所で呼び出す

if(select == 2){
        if(list.size() == 0){
          System.out.println("まだ遊ぶ猫がいません。。。");
          continue;
        }
        for(int i=0;i<list.size();i++){
          System.out.printf("%d・・・%s%n",i,list.get(i).showStatus());
        }
        System.out.print("どの猫と遊びますか?>>");
        int no = sc.nextInt();
        list.get(no).play();
        sortCat(list);
}

このように適切にメソッド分割を行うと処理の見通しがよくなる。

いざ実行!

それではコンパイルして実行してみよう。
実行例のようになれば成功だ。
以下にここまでの全ソースを張っておくのエラーが出た人は参考にしてもらいたい。

import java.util.*;
public class CatApp{
  public static void main(String[] args){
    Random rand=new Random();
    Scanner sc = new Scanner(System.in);
    final String[] TYPES={"黒","白","茶トラ"};
    ArrayList<Cat> list = new ArrayList<>();
    System.out.println("***猫集め***");
    while(true){
      System.out.print("1.集める 2.遊ぶ 3.終了>>");
      int select = sc.nextInt();
      if(select == 3){
        System.out.println("***結果***");
        for(Cat c :list){
          System.out.println(c.showStatus());
        }
        System.out.println("また遊んでね。おしまい");
        return;
      }
      if(select == 1){
        String type=TYPES[rand.nextInt(TYPES.length)];
        System.out.printf("%s猫を見つけた!%n",type);
        System.out.print("この猫に名前をつけてください>>");
        String name=sc.next();
        Cat cat = new Cat(name,type);
        list.add(cat);
        System.out.println(cat.name+"が仲間に加わった");
      }else 
      if(select == 2){
        if(list.size() == 0){
          System.out.println("まだ遊ぶ猫がいません。。。");
          continue;
        }
        for(int i=0;i<list.size();i++){
          System.out.printf("%d・・・%s%n",i,list.get(i).showStatus());
        }
        System.out.print("どの猫と遊びますか?>>");
        int no = sc.nextInt();
        list.get(no).play();
        sortCat(list);
      }
    }
  }
  static void sortCat(ArrayList<Cat> list){
    for(int i=0;i<list.size()-1;i++){
      for(int j=i+1;j<list.size();j++){
        if(list.get(i).intimacy < list.get(j).intimacy){
          Cat temp=list.get(i);
          list.set(i,list.get(j));
          list.set(j,temp);
        }
      }
    }
  }
}

class Cat{
  String name;
  String type;
  int intimacy=0;

  Cat(String name,String type){
    this.name=name;
    this.type=type;
  }
  
  String showStatus(){
    return String.format("%s[%s](%d)",this.name,this.type,this.intimacy);
  }

  void play(){
    System.out.println(this.name+"と遊んだ");
    System.out.println("...");
    System.out.println(this.name+"の親密度がアップした!");
    this.intimacy++;
  }
}

データの永続化

通常javaのコンソールアプリは一度実行して終了すると、それまでのデータは消えてしまう。今回でいうと、どんだけたくさん遊んで親密度をあげても次回遊ぶときは最初からだ。今回はファイルに書き込むことによってデータを永続化する。データ・フォーマットにはCSVを使う。

準備

ファイルの読み書きするクラスはjava.ioパッケージに属している。短い名前で使えるように以下のようにjava.ioパッケージをインポートする。

import java.util.*;
import java.io.*;
public class CatApp{
略
}

例外処理は行わなわないのでthrows 宣言を追加する。
本来ファイルの読み書きには例外処理が必要となるが、ここではそれを行わずthrows宣言だけで済ませる。mainメソッドを以下のように修正

import java.util.*;
import java.io.*;
public class CatApp{
  public static void main(String[] args) throws Exception{
略
}

ファイルオブジェクトの作成

今回、ゲーム初回時(セーブデータがない)状態なのか、セーブデータがある状態なのかで処理を分岐したいのでmainメソッド内を以下のように修正する。(今回セーブデータを保存するファイル名をcatlist.csvとする)

    String[] types={"黒","白","茶トラ"};
    File file = new File("catlist.csv");
    ArrayList<Cat> list;
    if(file.exists()){
      //セーブデータがあったらそれをもとにlistを作る処理

    }else{
      //初回時
      list=new ArrayList<>();
    }
    System.out.println("***猫集め***");

ファイルの読み込み処理

セーブされるデータは以下のような形式になる(名前、種類、親密度)
たま,茶トラ,2
なので、この3つの情報からもインスタンスが作成出来たほうが便利だ。
Catクラスに以下のコンストラクタを追記する。

  Cat(String name,String type){
    this.name=name;
    this.type=type;
  }
  Cat(String name,String type,int intimacy){
    this(name,type);
    this.intimacy=intimacy;
  }

ファイルの読み込み処理をメインメソッドに書いてもよいのだが、見通しが悪くなってしまうのでメソッド化しよう。さきほど作成した、sortCatメソッドの下に、以下のようにloadFileメソッドを作成する。

  static void sortCat(ArrayList<Cat> list){
    略
  }
  static ArrayList<Cat> loadFile(File file) throws Exception{
    //リターンするlistを作成
    ArrayList<Cat> list =new ArrayList<>();
    //読み込みはfis
    FileInputStream fis=new FileInputStream(file);
    InputStreamReader isr = new InputStreamReader(fis,"UTF-8");
    BufferedReader br = new BufferedReader(isr);
    //読み込み時のイディオム
    String line;
    while((line = br.readLine())!=null){
      //csvデータなのでカンマでsplit
      String[] values=line.split(",");
      //名前の取り出し
      String name=values[0];
      //猫種類の取り出し
      String type=values[1];
      //親密度の取り出し
      int intimacy = Integer.parseInt(values[2]);
      //3つの情報をもとにCatインスタンスを作成
      Cat c = new Cat(name,type,intimacy);
      //リストに加える
      list.add(c);
    }
    //br終了処理
    br.close();
    //作成されたlistを返却する
    return list;
  }

ファイル読み込みからオブジェクトを作成する機会はとても多い、イディオムとして処理をまるっと覚えてしまおう。

ファイルからlistを作れるようになったのでmainメソッドからこれを呼び出すように以下のように記述

    if(file.exists()){
      //セーブデータがあったらそれをもとにlistを作る処理
      list=loadFile(file);
    }else{
      //初回時
      list=new ArrayList<>();
    }

ファイルの書き込み処理

まずは、Catクラスのインスタンスが自分の情報をcsv形式にして出力できると便利そうだ。CatクラスにtoCSVメソッドを以下のように追記する

  void play(){
    System.out.println(this.name+"と遊んだ");
    System.out.println("...");
    System.out.println(this.name+"の親密度がアップした!");
    this.intimacy++;
  }
  String toCSV(){
    return String.format("%s,%s,%d",this.name,this.type,this.intimacy);
  }

それでは実際にファイルに書き込む処理を作成する。アプリケーションクラスにあるloadFileメソッドの下にsaveFileメソッドを以下のように作成する。

  static void saveFile(File file,ArrayList<Cat> list) throws Exception{
    //書き込みはfos
    FileOutputStream fos = new FileOutputStream(file);
    OutputStreamWriter osw = new OutputStreamWriter(fos,"UTF-8");
    BufferedWriter bw = new BufferedWriter(osw);

    //引数で受け取ったlistを拡張for文で回す
    for(Cat c :list){
      //インスタンスの情報をcsvで書き込む
      bw.write(c.toCSV());
      //改行
      bw.newLine();
    }
    //ファイルを閉じる
    bw.close();
  }

作成したメソッドをアプリが終了する時に呼び出せばよい。mainメソッドに以下の1行を追記する。

      if(select == 3){
        System.out.println("***結果***");
        for(Cat c :list){
          System.out.println(c.showStatus());
        }
        System.out.println("また遊んでね。おしまい");
        saveFile(file,list);
        return;
      }

完成!

実行してみよう。
一度アプリを終了しても、次回実行時に続きから遊べることがわかる。
このようにちょっとしたデータの永続化にファイルの書き込みは便利なので覚えておくとよいだろう。

以下に全ソースコードを記すので、エラーが出た人は参照してもらいたい。

import java.util.*;
import java.io.*;
public class CatApp{
  public static void main(String[] args) throws Exception{
    Random rand=new Random();
    Scanner sc = new Scanner(System.in);
    final String[] TYPES={"黒","白","茶トラ"};
    File file = new File("catlist.csv");
    ArrayList<Cat> list;
    if(file.exists()){
      //セーブデータがあったらそれをもとにlistを作る処理
      list=loadFile(file);
    }else{
      //初回時
      list=new ArrayList<>();
    }
    System.out.println("***猫集め***");
    while(true){
      System.out.print("1.集める 2.遊ぶ 3.終了>>");
      int select = sc.nextInt();
      if(select == 3){
        System.out.println("***結果***");
        for(Cat c :list){
          System.out.println(c.showStatus());
        }
        System.out.println("また遊んでね。おしまい");
        saveFile(file,list);
        return;
      }
      if(select == 1){
        String type=TYPES[rand.nextInt(TYPES.length)];
        System.out.printf("%s猫を見つけた!%n",type);
        System.out.print("この猫に名前をつけてください>>");
        String name=sc.next();
        Cat cat = new Cat(name,type);
        list.add(cat);
        System.out.println(cat.name+"が仲間に加わった");
      }else 
      if(select == 2){
        if(list.size() == 0){
          System.out.println("まだ遊ぶ猫がいません。。。");
          continue;
        }
        for(int i=0;i<list.size();i++){
          System.out.printf("%d・・・%s%n",i,list.get(i).showStatus());
        }
        System.out.print("どの猫と遊びますか?>>");
        int no = sc.nextInt();
        list.get(no).play();
        sortCat(list);
      }
    }
  }
  static void sortCat(ArrayList<Cat> list){
    for(int i=0;i<list.size()-1;i++){
      for(int j=i+1;j<list.size();j++){
        if(list.get(i).intimacy < list.get(j).intimacy){
          Cat temp=list.get(i);
          list.set(i,list.get(j));
          list.set(j,temp);
        }
      }
    }
  }
  static ArrayList<Cat> loadFile(File file) throws Exception{
    //リターンするlistを作成
    ArrayList<Cat> list =new ArrayList<>();
    //読み込みはfis
    FileInputStream fis=new FileInputStream(file);
    InputStreamReader isr = new InputStreamReader(fis,"UTF-8");
    BufferedReader br = new BufferedReader(isr);
    //読み込み時のイディオム
    String line;
    while((line = br.readLine())!=null){
      //csvデータなのでカンマでsplit
      String[] values=line.split(",");
      //名前の取り出し
      String name=values[0];
      //猫種類の取り出し
      String type=values[1];
      //親密度の取り出し
      int intimacy = Integer.parseInt(values[2]);
      //3つの情報をもとにCatインスタンスを作成
      Cat c = new Cat(name,type,intimacy);
      //リストに加える
      list.add(c);
    }
    //br終了処理
    br.close();
    //作成されたlistを返却する
    return list;
  }
  static void saveFile(File file,ArrayList<Cat> list) throws Exception{
    //書き込みはfos
    FileOutputStream fos = new FileOutputStream(file);
    OutputStreamWriter osw = new OutputStreamWriter(fos,"UTF-8");
    BufferedWriter bw = new BufferedWriter(osw);

    //引数で受け取ったlistを拡張for文で回す
    for(Cat c :list){
      //インスタンスの情報をcsvで書き込む
      bw.write(c.toCSV());
      //改行
      bw.newLine();
    }
    //ファイルを閉じる
    bw.close();
  }
}

class Cat{
  String name;
  String type;
  int intimacy=0;

  Cat(String name,String type){
    this.name=name;
    this.type=type;
  }
  Cat(String name,String type,int intimacy){
    this(name,type);
    this.intimacy=intimacy;
  }
  
  String showStatus(){
    return String.format("%s[%s](%d)",this.name,this.type,this.intimacy);
  }

  void play(){
    System.out.println(this.name+"と遊んだ");
    System.out.println("...");
    System.out.println(this.name+"の親密度がアップした!");
    this.intimacy++;
  }
  String toCSV(){
    return String.format("%s,%s,%d",this.name,this.type,this.intimacy);
  }
}

演習

以下に今回と同様のロジックで作成できる演習問題を用意した。やってみよう!
ToDoアプリの作成

Java
スポンサーリンク
シェアする
mjpurinをフォローする

コメント

タイトルとURLをコピーしました