円周率タイピングゲーム

 言うまでもないが非常にマニアックなタイピングゲームである。
打つのは数字だけである。

 また、タイピングゲームである以上、キー入力しか受け付けない。
このゲームの特色は、ハイスコアを記録しておくことである。
同じディレクトリ内のscoreファイルに、今までの最高得点を書き込む。

 最終課題として、盛りだくさんなプログラムにしたため、
ゲームとしては少々中途半端かもしれない。
だが、このゲームは円周率をこよなく愛する人たちのために作られたものであって、
あまり一般人を対象にして作ったつもりはないのだから、
これで十分といえば十分だろう。

 実際にプログラムを見てみよう。
   // AWTを使うため,
import java.awt.*;
  // イベント駆動関係のクラスを用いるため
import java.awt.event.*;
  // 乱数等を用いるため
import java.util.*;
  // 入出力関係のクラスを用いるため
import java.io.*;
 特に説明は要らないと思うが、毎度お約束のimportである。
次に、様々な変数を定義する。少し冗長かもしれない。
 // インタフェースの実装では 「implements インタフェース名」と書く
 // 複数書く場合は,カンマで区切る
class Kadai1216 extends Frame implements Runnable{
   // ゲーム全体を5つの段階に分けて、
   // オープニング画面・設定画面・ゲーム画面・結果発表・終了とする
   // その段階を識別するint型の変数stageの宣言
  int stage = 0;
   // 赤
  int red = 0;
   // 緑
  int green = 0;
   // 青
  int blue = 0;
   // 乱れ打ちモードoff
  int rifle =0;
  int bag = 0;
   // 乱れ打ちモード用の数字;
  int riflecheck = 0;
   // 二重表示の禁止
  int doublepaint = 0;
   // 何番目の数字かを示すint型の変数、countの宣言
  int count = 0;
   // レベルにあわせて一度に加点する数字を調整
  int level = 1;
   // 表示するリンゴに書かれた数を表す変数clickの宣言
  int click = 3;
   // 時間計測機能
  int time = 0;
  int timesum = 0;
   // 正しく押された数字の数を示すint型の変数checkの宣言
  int check = 0;
   // 二重得点を防ぐためのint型の変数doublepointの宣言
  public int doublepoint = 0;
   // リンゴ表示のスピード
  int apple = 2000;
   // maniacnum(途中開始ナンバー)の設定
  int maniacnum;
   // 最高得点
  int highscore = 0;
   // 後にcheckを文字列にしてgetpointと定義する  
  String getpoint = check + "";
   // イメージを表す Image クラスの変数 image の宣言
  Image image1,image2,image3,image4,image5;
  Image image6,image7,image8,image9,image0;
 次に、イメージファイルを呼び出し、キーイベントが発生したときの
イベントリスナを作る。
ゲームが、オープニング画面、設定画面、ゲーム画面、終了画面、ハイスコア表示画面、
の五つに分かれているために、if 構文がうざったい。
 もっといい方法があれば知りたい。
   // Kadai1216クラスのコンストラクタ
  public Kadai1216(){
     // 親クラスである Frameのコンストラクタを呼ぶ
    super();
     // イメージを得る.
    image1=Toolkit.getDefaultToolkit().getImage("apple1.gif");
    image2=Toolkit.getDefaultToolkit().getImage("apple2.gif");
    image3=Toolkit.getDefaultToolkit().getImage("apple3.gif");
    image4=Toolkit.getDefaultToolkit().getImage("apple4.gif");
    image5=Toolkit.getDefaultToolkit().getImage("apple5.gif");
    image6=Toolkit.getDefaultToolkit().getImage("apple6.gif");
    image7=Toolkit.getDefaultToolkit().getImage("apple7.gif");
    image8=Toolkit.getDefaultToolkit().getImage("apple8.gif");
    image9=Toolkit.getDefaultToolkit().getImage("apple9.gif");
    image0=Toolkit.getDefaultToolkit().getImage("apple0.gif");
     // GUI部品と,Event Listenerを関連づける
    addKeyListener(new KeyAdapter(){
       // キーが押された時にこのメソッドが呼ばれる.
      public void keyPressed(KeyEvent e){
	int key=e.getKeyChar();
        if (stage != 2){
	   // デバッグ用の表示
	  System.out.println("keyPressed("+e+")");
	}
	if (stage == 2){
	  if (key < '0'){
	   // デバッグ用の表示
	  System.out.println("keyPressed("+e+")");
	  }
	  if (key > '9'){
	   // デバッグ用の表示
	  System.out.println("keyPressed("+e+")");
	  }
	}
	if (stage > 2){
	  if (key == 'q'){
	    System.exit(0);
	  }
	}
        if(key == 'q'){
	  stage = 3;
	}
	if(key == 'p'){
	  stage = 4;
	}
	if (key == 's'){
	  stage = 1;
	}
	if (stage == 2){
	  if (doublepoint == 0){
	     // key を、普通のint 型の数に戻す
	    key = key - 48;
	     // デバッグ用の表示
	    System.out.println(key + "," + click);
	     // key とclick が一致したら得点とする
	    if(key == click){
	      riflecheck =1;
	      if(rifle == 0){
	        check += level;
	      }
	      if(rifle == 1){
	        System.out.println(time);
	        check += 2 - time / 100000000;
		time = 0;
	      }
	    }
	    if (rifle == 1){
	      if(key != click){
	        riflecheck = 1;
	        stage = 3;
	      }
	    }
	  }
	}
	if (key == 'w'){
	  stage = 2;
	}
	if (stage == 0){
	  if (key == 'm'){
	    stage = 1;
	  }
	}	
	if (stage == 1){
	  if(rifle == 0){
	    if (key > '0' & key < '6'){
	      key = key - '0';
	      apple = 2000 / key;
	      level = key * 2 - 1; 
	    }
            if(key == 'm'){
              apple = 100;
              rifle = 1;
              level = 1;
            }	  
	  }
	  if(rifle == 1){
	    if (key >= '0' & key <= '9'){
	      key = key - '0';
              maniacnum = key * 1000;
	    }	  
	  }
        }
	doublepoint ++;
      }
    });
     // スクリーンのサイズを定める
    setSize(600,600);
    setVisible(true);
  }
 次にスレッドを走らせなければならない。
やはり、五つに場合分けをしてしまう。
  public void run(){
    for(;;){      
       // (appleミリ秒)待つ
      try {
        Thread.sleep(apple);
      }
      catch(Exception e){}
      
      if (stage == 0){
         // オープニング画像をpaintする
        repaint();
      }
      if (stage == 1){
         // 設定画像を表示
        repaint();
      }
      if (stage == 2){
        if (rifle == 1){
	  if (doublepaint == 0){
	    nextNumber();
	  }
	  doublepaint ++;
	  while(true){
	    if (time < 300000000){
	      time ++;
	    }
	    if (riflecheck == 1){
	      break;
	    }
	    if (stage == 3){
	      break;  
	    }
	  }
	riflecheck = 0;
	}
         // Kadai1216クラスのインスタンス frame のnextNumberメソッドの呼び出し
        nextNumber();
      }
      if (stage == 3){
         // 終了画像の呼び出し、得点の保存
        repaint();
      }
      if (stage == 4){
         // 最高得点の表示
        repaint();
      }
    }
  }
 必要不可欠なmainメソッド
  public static void main(String[] args){
     // Kadai1216クラスのインスタンスを作成
    Thread th=new Thread(new Kadai1216());
     // スレッド th の実行開始
    th.start();
  }
 nextNumber メソッドで、pai.txt から円周率を取り出す。
取り出した円周率は、ベクトルに収納する。
  void nextNumber(){
     // Vector クラスのオブジェクトの作成
    Vector pai = new Vector();
     // エラーが出てきたときに備えtryを使う
    try {
      // FileReaderオブジェクトを作成する
      FileReader fr = new FileReader("pai.txt");

      // ループの作成
      int i;
      // 桁数を表す変数の宣言
      int j = 0;
      // 文字を読み取る
      while((i = fr.read()) != -1) {
        i = i - '0';
	pai.addElement(new Integer(i));
	j++;
      }

       // ベクトルの要素を抜き出してint型にして表示
      Object clicks = pai.elementAt(count + maniacnum);
      String circle = clicks.toString();
      click = Integer.parseInt(circle);
       // 新しい数字が出てきたからcountを1増やす
      count++;

      // FileReaderをクローズする
      fr.close();
    }
     // エラーを捕まえる
    catch(Exception e) {
      System.out.println("Exception: " + e);
    }
   // 数字が新しくなったところで再表示    
  repaint();
  }
 画面のちらつきをなくすためのオフスクリーンイメージ
  
   // オフスクリーンイメージを準備する
  Image offScreenImage;
  Graphics offScreenGraphics;
  public void update(Graphics g){
    if(offScreenImage==null){
      offScreenImage=createImage(600,600);
       // オフスクリーンイメージを600x400のサイズで作成
      offScreenGraphics=offScreenImage.getGraphics();
       // オフスクリーンイメージに描画する
    }
    paint(offScreenGraphics); // 次の画面のイメージを作る.
    g.drawImage(offScreenImage,0,0,this); // イメージを本物のスクリーンに書き込む
  }
 画面にペイントするメソッド。
やはりこれも五段階に分かれて、ちょっとしつこい。
   // Frameの書き直しをする際に呼ばれる
  public void paint(Graphics g){
     // doublepointの値をクリア
    doublepoint = 0;
 オープニング画面。
十種のリンゴを一回すべて表示させる。
ストレスを感じるほどに時間がかかるが、
ゲームの最中にイメージを呼び出すわけにもいかない。
     // 何段階目のステージにあるか識別
    if (stage == 0){
       // 黄色のグラデーションで画面を塗りつぶす
      red = 255;
      green = 255;
      blue = 0;
      for (int i = 0;i < 200;i += 1){               
        g.setColor(new Color(red,green,blue + i));
        g.fillRect(0,i*3,600,i*3+3);
      }      
       // 十種類のリンゴの絵をすべて表示
      g.drawImage(image1,100,100,this);
      g.drawImage(image2,100,200,this);
      g.drawImage(image3,100,300,this);
      g.drawImage(image4,100,400,this);
      g.drawImage(image5,100,500,this);
      g.drawImage(image6,500,100,this);
      g.drawImage(image7,500,200,this);
      g.drawImage(image8,500,300,this);
      g.drawImage(image9,500,400,this);
      g.drawImage(image0,500,500,this);
       // フォントを設定する
      g.setColor(Color.black);
      g.setFont(new Font("Serif", Font.BOLD, 36));
      g.drawString("Catch the apple !",200,200);
      g.drawString("Apple pie !",200,310);
       // フォントの再設定
      g.setFont(new Font("Serif", Font.BOLD, 18));
      g.drawString("start : w",250,350);
      g.drawString("speed : s",250,375);
      g.drawString("quit  : q",250,400);
    }
 設定の画面。この中にもマニアックモードがあるため、
少ししつこいプログラムだと思う。
    if (stage == 1){
       // 黄色と緑のグラデーションで画面を塗りつぶす
      red = 0;
      green = 255;
      blue = 0;
      for (int i = 0;i < 200;i++){
        g.setColor(new Color(red + i,green,blue));
        g.fillRect(i*3,0,i*3+3,600);
      }
         // 十種類のリンゴの絵をすべて表示
        g.drawImage(image1,100,100,this);
        g.drawImage(image2,100,200,this);
        g.drawImage(image3,100,300,this);
        g.drawImage(image4,100,400,this);
        g.drawImage(image5,100,500,this);
        g.drawImage(image6,500,100,this);
        g.drawImage(image7,500,200,this);
        g.drawImage(image8,500,300,this);
        g.drawImage(image9,500,400,this);
        g.drawImage(image0,500,500,this);      
       // フォントを設定する
      g.setColor(Color.black);
      g.setFont(new Font("Serif", Font.BOLD, 36));
      if (rifle == 0){
         // 黄色とマゼンタのグラデーションで画面を塗りつぶす
        red = 255;
        green = 255;
        blue = 0;
        for (int i = 0;i < 200;i++){
          g.setColor(new Color(red,green - i,blue + i));
          g.fillRect(i*3,0,i*3+3,600);
        }
         // 十種類のリンゴの絵をすべて表示
        g.drawImage(image1,100,100,this);
        g.drawImage(image2,100,200,this);
        g.drawImage(image3,100,300,this);
        g.drawImage(image4,100,400,this);
        g.drawImage(image5,100,500,this);
        g.drawImage(image6,500,100,this);
        g.drawImage(image7,500,200,this);
        g.drawImage(image8,500,300,this);
        g.drawImage(image9,500,400,this);
        g.drawImage(image0,500,500,this);
  	g.setColor(new Color(0,0,0));
        g.drawString("Speed level",150,150);
        g.drawString("level 1~5",150,180);
         // フォントの再設定
        g.setFont(new Font("Serif", Font.BOLD, 18));
        g.drawString("1 : 2sec",250,220);
        g.drawString("2 : 1sec",250,260);
        g.drawString("3 : 2/3sec",250,300);
        g.drawString("4 : 1/2sec",250,340);
        g.drawString("5 : 2/5sec",250,380);
        g.drawString("Start :w",250,460);
        g.drawString("Maniac mode :m",250,420);
      }
      if (rifle == 1){
        g.drawString("Welcome to",200,150);
        g.drawString("Maniac Mode",200,180);
        g.drawString("Select Start number",190,220);
        g.drawString("0    : 0",250,260);
        g.drawString("1000 : 1",250,300);
        g.drawString("2000 : 2",250,340);
        g.drawString("........",250,380);
        g.drawString("Start :w",250,460);
      } 
    }
 ゲーム画面。乱数を取得し、リンゴの絵を様々な場所に表示させる。
真ん中に得点カウンタを設置する。
    else if (stage == 2){
       // 表示する場所をnumx,numyで宣言
      int numx,numy;
       // 乱数ジェネレータを作成する
      Random generator = new Random();
       // x座標を乱数で生成する
      numx = generator.nextInt() % 100;
      if (numx<0)
        numx = 1-numx;
       // y座標を乱数で生成する
      numy = generator.nextInt() % 100;
      if (numy<0)
        numy = 1-numy;
       // このままでは範囲が狭く、偏りがあるので、
      numx = 50 + numx * 5;
      numy = 50 + numy * 5; 
       // デバッグ用の表示
      System.out.println(click + "," + numx+ "," + numy);
      g.setColor(new Color(0,0,255));
      g.fillRect(0,0,600,600);
      
    
       // 文字色の指定
      g.setColor(Color.black);
       // フォントを設定する
      g.setFont(new Font("Serif", Font.BOLD, 36));
       // フォントメトリックスを取得する
      FontMetrics fm = g.getFontMetrics();
       // 得点カウンタの表示
      getpoint = "" + check;
      Dimension d = getSize();
      int onlyx =d.width / 2 - fm.stringWidth(getpoint)/2;
      g.drawString(getpoint ,onlyx ,d.height/2);   

       // 何番目の絵かで分類して
      if(click == 1){
         // イメージ image1 を (numx,numy)を左上にして表示する.
        g.drawImage(image1,numx,numy,this);
      }
      else if(click == 2){
       // イメージ image2 を (numx,numy)を左上にして表示する.
        g.drawImage(image2,numx,numy,this);
      }
      else if(click == 3){
       // イメージ image3 を (numx,numy)を左上にして表示する.
        g.drawImage(image3,numx,numy,this);
      }
      else if(click == 4){
       // イメージ image4 を (numx,numy)を左上にして表示する.
        g.drawImage(image4,numx,numy,this);
      }
      else if(click == 5){
       // イメージ image5 を (numx,numy)を左上にして表示する.
        g.drawImage(image5,numx,numy,this);
      }
      else if(click == 6){
       // イメージ image6 を (numx,numy)を左上にして表示する.
        g.drawImage(image6,numx,numy,this);
      }
      else if(click == 7){
       // イメージ image7 を (numx,numy)を左上にして表示する.
        g.drawImage(image7,numx,numy,this);
      }
      else if(click == 8){
       // イメージ image8 を (numx,numy)を左上にして表示する.
        g.drawImage(image8,numx,numy,this);
      }
      else if(click == 9){
       // イメージ image9 を (numx,numy)を左上にして表示する.
        g.drawImage(image9,numx,numy,this);
      }
      else{
       // イメージ image0 を (numx,numy)を左上にして表示する.
        g.drawImage(image0,numx,numy,this);
      }
    }
 結果表示画面。score.txt にしまわれている点数と比較し、
ハイスコアを更新する。
ただし、この画面に表示されるのは今回の得点。
    else if (stage == 3){
       // 黄色のグラデーションで画面を塗りつぶす
      red = 255;
      green = 255;
      blue = 200;
      for (int i = 0;i < 200;i++){
        g.setColor(new Color(red,green,blue - i));
        g.fillRect(0,i*3,600,i*3+3);
      }      
       // 十種類のリンゴの絵をすべて表示
      g.drawImage(image1,100,100,this);
      g.drawImage(image2,100,200,this);
      g.drawImage(image3,100,300,this);
      g.drawImage(image4,100,400,this);
      g.drawImage(image5,100,500,this);
      g.drawImage(image6,500,100,this);
      g.drawImage(image7,500,200,this);
      g.drawImage(image8,500,300,this);
      g.drawImage(image9,500,400,this);
      g.drawImage(image0,500,500,this);
       // フォントを設定する
      g.setColor(Color.black);
      g.setFont(new Font("Serif", Font.BOLD, 36));
      g.drawString(getpoint,200,100);
      g.drawString("Good bye",200,200);
      g.drawString("See you",200,300);
       // フォントの再設定
      g.setFont(new Font("Serif", Font.BOLD, 18));
      g.drawString("Highscore : p",250,450);
      g.drawString("Quit : q",250,400);

      if (bag == 0){
        try {
           // FileReaderオブジェクトを作成する
          FileReader scorer = new FileReader("score.txt");
 	
           // 文字を読み取って、表示する
          int i;
          while((i = scorer.read()) != -1) {
            System.out.println(i);
            highscore = highscore *10 + (i - 48);
          }	
      
           // FileReaderをクローズする
          scorer.close();
        }
        catch(Exception e) {
          System.out.println("Exception: " + e);
        }
         // デバッグ用の表示
        System.out.println(check);
        System.out.println(highscore);
      bag ++;
      }
      if (check > highscore){
        try {
           // FileWriterオブジェクトを作成する
          FileWriter scorew = new FileWriter("score.txt");

           // 得点をファイルにしまう
          scorew.write(getpoint);
      
           // FileWriterをクローズする
          scorew.close();
        }
        catch(Exception e) {
          System.out.println("Exception: " + e);
        }
        highscore = check;  
      }	
    }        
 最後、ハイスコアとして登録されている数字を書き出す。
    else if (stage == 4){
       // 黄色と青のグラデーションで画面を塗りつぶす
      red = 255;
      green = 255;
      blue = 0;
      for (int i = 0;i < 200;i++){
        g.setColor(new Color(red,green - i,blue + i));
        g.fillRect(0,i*3,600,i*3+3);
      }      
       // 十種類のリンゴの絵をすべて表示
      g.drawImage(image1,100,100,this);
      g.drawImage(image2,100,200,this);
      g.drawImage(image3,100,300,this);
      g.drawImage(image4,100,400,this);
      g.drawImage(image5,100,500,this);
      g.drawImage(image6,500,100,this);
      g.drawImage(image7,500,200,this);
      g.drawImage(image8,500,300,this);
      g.drawImage(image9,500,400,this);
      g.drawImage(image0,500,500,this);
       // フォントを設定する
      g.setColor(Color.black);
      g.setFont(new Font("Serif", Font.BOLD, 36));
      g.drawString("Highscore",200,200);
      String HighScore = highscore + "";
      g.drawString(HighScore,200,300);
    }
  }
}