/**************************************************************
    Mole.java 
        Copyright (C) 1999 by Kazunori Iwata
                              (kiwata@egg.ics.nitech.ac.jp)
        last modified 1999.10.13
**************************************************************/

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;

public class Mole extends Applet implements Runnable,MouseListener
{
  Thread thread = null;  // ゲーム用スレッド
  char[] title = {'m','o','l','e','!'};  // タイトル文字列
  Color bgcolor = Color.black;  // 背景色
  String message = "START";  // ボタンの文字
  Font font = new Font("TimesRoman",Font.BOLD,20);
  FontMetrics fmetrics;
  Point button = new Point(90,280);  // ボタンの位置座標
  Image[] img = new Image[4];  // モグラの絵を格納
  Point[] point = new Point[12];  // 絵の位置座標を格納
  int interval = 800;  // ポーズ時間
  int total = 30;  // モグラの出現回数
  int faster = 6;  // １得点ごとに（ポーズ時間が）速くなる時間
  int time;  // 残りの出現回数
  int[] state = new int[12];  // 絵番号を格納
  int score;  // 得点
  int img_width = 60;  // 絵の幅
  int img_height = 60;  // 絵の高さ
  int margin_width = 30;  // 欄外の幅
  int margin_height = 60;  // 欄外の高さ
  boolean stopped;  // ループ用のフラグ
  Dimension offd;
  Image offImage;  // オフスクリーンイメージ
  Graphics offg;  // オフスクリーンバッファ
  MediaTracker tracker;
  AudioClip clip1,clip2,clip3;  // オーディオクリップ

  public void init()
    {
      int i;
      String parameter;  // パラメータを格納
      
      // マウスリスナーの設定
      addMouseListener(this);

      // 背景色の設定
      parameter = getParameter("bgcolor");
      if(parameter != null)
	{
	  String hex_red = parameter.substring(0,2);
	  String hex_green = parameter.substring(2,4);
	  String hex_blue = parameter.substring(4);
	  int red = Integer.valueOf(hex_red,16).intValue();
	  int green = Integer.valueOf(hex_green,16).intValue();
	  int blue = Integer.valueOf(hex_blue,16).intValue();
	  bgcolor = new Color(red,green,blue);
	}
      
      // ポーズ時間の設定
      parameter = getParameter("interval");
      if(parameter != null)
	interval = Integer.valueOf(parameter,10).intValue();
      
      // モグラの出現回数の設定
      parameter = getParameter("total");
      if(parameter != null)
	total = Integer.valueOf(parameter,10).intValue();

      // 1得点ごとに速くなる時間の設定
      parameter = getParameter("faster");
      if(parameter != null)
	faster = Integer.valueOf(parameter,10).intValue();
      
      // オーディオクリップの設定
      parameter = getParameter("hitaudio");
      if(parameter != null)
	clip1 = getAudioClip(getCodeBase(),parameter);
      
      parameter = getParameter("missaudio");
      if(parameter != null)
	clip2 = getAudioClip(getCodeBase(),parameter);
      
      parameter = getParameter("pressaudio");
      if(parameter != null)
	clip3 = getAudioClip(getCodeBase(),parameter);
      
      // イメージをtrackerに登録
      tracker = new MediaTracker(this);
      for(i = 0;i < 4;i++)
	{
	  String filename = Integer.toString(i) + ".gif";
	  img[i] = getImage(getCodeBase(),filename);
	  tracker.addImage(img[i],0,img_width,img_height);
	}
      
      // アプレットサイズを取得
      offd = getSize();
      
      // オフスクリーンイメージ、オフスクリーンバッファを取得
      offImage = createImage(offd.width,offd.height);
      offg = offImage.getGraphics();
      
      // 変数の初期化
      fmetrics = getFontMetrics(font);
      stopped = true;
      reset();
      
      // 絵の位置座標を計算
      int n,m,x,y;
      i = 0;
      y = margin_height;
      for(m = 0;m < 3;m++)
	{
	  x = margin_width;
	  for(n = 0;n < 4;n++)
	    {
	      point[i++] = new Point(x,y);
	      x += img_width;
	    }
	  y += img_height;
	}
    }
  
  public void paint(Graphics g)
    {
      update(g);
    }
  
  public void update(Graphics g)
    {
      int i;
      Dimension d = getSize();
      
      // イメージのロードがされていない場合はロード
      if(!tracker.checkID(0))
	{
	  g.clearRect(0,0,d.width,d.height);
	  g.drawString("Now loading image",d.width/4,d.height/2);
	}
      else
	{
	  // アプレットサイズに変更があった場合は、
	  // オフスクリーンイメージ、オフスクリーンバッファを変更
	  if(d.width != offd.width || d.height != offd.height)
	    {
	      offd = d;
	      offImage = createImage(offd.width,offd.height);
	      offg = offImage.getGraphics();
	    }
	  
	  // 欄外を描画
	  offg.setColor(Color.black);
	  offg.fillRect(0,0,offd.width,offd.height);
	  
	  // タイムゲージを描画
	  offg.setFont(new Font("TimesRoman",Font.PLAIN,18));
	  offg.setColor(Color.white);
	  offg.drawString("TIME",margin_width,25);
	  offg.setColor(Color.gray);
	  offg.fillRect(margin_width + 5,35,120*time/total,18);
	  offg.setColor(Color.red);
	  offg.fillRect(margin_width,30,120*time/total,18);
	  
	  // 得点の描画
	  offg.setFont(new Font("TimesRoman",Font.PLAIN,18));
	  offg.setColor(Color.white);
	  offg.drawString("SCORE",200,25);
	  offg.setFont(new Font("TimesRoman",Font.BOLD,20));
	  offg.drawString(Integer.toString(score),220,45);

	  // スタートボタンの描画
	  if(time == 0 || time == total)
	    {
	      offg.setFont(font);
	      offg.setColor(Color.yellow);
	      offg.drawString(message,button.x,button.y);
	    }
	  
	  // 残り時間があれば絵を描画。なければコメントを描画
	  if(time > 0)
	    {
	      // 背景を描画
	      offg.setColor(bgcolor);
	      offg.fillRect(0,margin_height,offd.width,img_height*3);

	      // 絵を描画
	      for(i = 0;i < 12;i++)
		offg.drawImage(img[state[i]],point[i].x,point[i].y,img_width,img_height,this);

	      // タイトルの描画
	      offg.setFont(new Font("TimesRoman",Font.BOLD,12));
	      offg.setColor(Color.gray);
	      offg.drawChars(title,0,title.length,210,290);
	      offg.setColor(Color.green);
	      offg.drawChars(title,0,
			     (total - time) % (title.length + 1),
			     210,290);
	    }
	  else
	    {
	      // コメントを描画
	      offg.setFont(new Font("TimesRoman",Font.BOLD,20));
	      offg.setColor(Color.white);
	      offg.drawString(getComment(score),margin_width,120);
	      offg.setFont(new Font("TimesRoman",Font.PLAIN,10));
	      offg.drawString("  Press START key !",margin_width,180);
	    }
	  
	  g.drawImage(offImage,0,0,this);
	}
    }
  
  public void start()
    {
      if(thread == null)
	{
	  thread = new Thread(this);
	  thread.start();
	}
    }
  
  public void stop()
    {
      // スレッドを止める
      thread = null;
      
      // オーディオクリップを止める
      if(clip1 != null) clip1.stop();
      if(clip2 != null) clip2.stop();
      if(clip3 != null) clip3.stop();
    }
  
  public void run()
    {
      int i;
      Graphics g = getGraphics();
      
      // イメージのロードを待つ
      try
	{
	  tracker.waitForID(0);
	}catch(InterruptedException e)
	{
	}
      
      // 再描画
      repaint();
      
      // スタートボタンが押されるまで待つ
      while(stopped)
	{
	  delay(1000);
	}
      
      // 実行中のスレッドを調べる
      Thread current_thread = Thread.currentThread();
      
      // 時間がなくなるまで繰り返す
      while(thread == current_thread && time > 0)
	{
	  // 乱数でモグラを出す穴番号を決定
	  state[(int)(Math.random()*(float)12)] = 1;
	  
	  // 再描画
	  repaint();
	  
	  delay(interval - score*faster);
	  
	  // 元に戻す
	  for(i = 0;i < 12;i++) state[i] = 0;
	  
	  time--;
	}
      
      delay(1500);
      repaint();
      stopped = true;
      
      // 結果を表示して、スタートボタンが押されるまで待つ
      while(stopped)
	{
	  delay(1000);
	}
    }
  
  // マウスボタンが押された場合の処理
  public void mousePressed(MouseEvent e)
    {
      int x = e.getX();  // x座標
      int y = e.getY();  // y座標

      // スタートボタンを押した場合
      if(stopped && isInButton(x,y))
	{
	  // 音を鳴らす
	  if(clip3 != null) clip3.play();
	  
	  delay(550);
	  
	  // 再ゲームの場合
	  if(time == 0)
	    {
	      stop();
	      reset();
	      start();
	      repaint();
	    }
	  // ゲームをスタートさせる場合
	  else stopped = false;
	}
      // 絵を押した場合
      else if(!stopped && time > 0)
	{
	  // 押された位置の穴番号を調べる
	  int n = toHallNumber(x,y);
	  
	  // モグラを叩いた場合
	  if(n != -1 && state[n] == 1)
	    {
	      // 得点を加算
	      score++;

	      // モグラが叩かれている絵をセット
	      state[n] = 2;

	      // 音を鳴らす
	      if(clip1 != null) clip1.play();

	      repaint(point[n].x,point[n].y,img_width,img_height);
	    }
	  // 外れた場合
	  else if(n != -1)
	    {
	      // 空振りした絵をセット
	      state[n] = 3;
	      
	      // 音を鳴らす
	      if(clip2 != null) clip2.play();

	      repaint(point[n].x,point[n].y,img_width,img_height);
	    }
	}
    }

  // マウスボタンが離された場合の処理
  public void mouseReleased(MouseEvent e){}
  
  // マウスボタンがクリックされた場合の処理
  public void mouseClicked(MouseEvent e){}

  // マウスカーソルが入ってきた場合の処理
  public void mouseEntered(MouseEvent e){}

  // マウスカーソルが出て行った場合の処理
  public void mouseExited(MouseEvent e){}

  // 変数を初期化するメソッド
  void reset()
    {
      int i;
      score = 0;
      time = total;
      for(i = 0;i < 12;i++) state[i] = 0;
    }
  
  // スレッドをスリープするメソッド
  void delay(int sleep_time)
    {
      try
	{
	  thread.sleep(sleep_time);
	}catch(InterruptedException e)
	  {
	  }
    }
  
  // スコアに応じてコメントを返すメソッド
  String getComment(int n)
    {
      float per = (float)n / (float)total;  // 成功率
      
      if((int)per == 1) return "Perfect !";
      else if(per >= 0.85) return "Great !";
      else if(per >= 0.7) return "Cool !";
      else if(per >= 0.6) return "Good !";
      else return "Poor ...";
    }
  
  // 引数x,y座標がスタートボタン内にあるなら真を返すメソッド
  boolean isInButton(int x,int y)
    {
      int width = fmetrics.stringWidth(message);  // ボタンの幅
      int height = fmetrics.getHeight();  // ボタンの高さ

      if(button.x <= x && button.y - height <= y
	 && button.x + width > x && button.y > y)
	return true;
      else return false;
    }
  
  // 押した位置座標に該当する穴番号を返すメソッド
  // 該当する穴番号がなければ-1を返す
  int toHallNumber(int x,int y)
    {
      int i,col;
      
      // y座標から行を求める
      if(point[0].y <= y && point[0].y + img_height > y) col = 0;
      else if(point[4].y <= y && point[4].y + img_height > y) col = 4;
      else if(point[8].y <= y && point[8].y + img_height > y) col = 8;
      else return -1;
      
      // x座標とcolから穴番号を求める
      for(i = col;i < col + 4;i++)
	{
	  if(point[i].x <= x && point[i].x + img_width > x)
	    return i;
	}

      // 該当する穴番号がなければ-1をかえす
      return -1;
    }
}
