/* 3-D Maze Applet
   JDK1.1対応版
                  Coded By.KeN    Mar.19,1997

   <param name=... value=...>で使えるパラメータ
    se0:歩行音 ファイル名から .au を除いた名前
    se1:ドアを開く音
    se2:スタート音
    se3:ゴール音
    x,y:スタート位置の座標
  vx,vy:最初に向いているベクトル (1,0)東 (-1,0)西 (0,1)南 (0,-1)北
  xsize:マップ全体のX方向のサイズ
  ysize:マップ全体のY方向のサイズ
    map:マップファイル名から .map を除いた名前
*/

import java.applet.AudioClip; // 音声用クラス
import java.awt.*;
import java.awt.event.*;
import java.net.URL;
import java.net.MalformedURLException;
import java.io.*;
import java.lang.StringIndexOutOfBoundsException;

public class ThreeDMaze extends java.applet.Applet implements KeyListener {
  int x, y;        // 現在地
  int vx, vy;      // 向いているベクトル
  int pr;         // 距離
  int f, l, r;      // 前方、左方、右方の状況
  AudioClip se[]; // 音声用配列
  int whatplay;   // 何番の音を鳴らすかを決めます

  int map[][];    // マップデータ

  public void init() { // アプレットが呼び出されると最初にこのメソッド
		       // を実行します
    String s;
    char k[] = null;
    InputStream is = null;    // InputStreamクラスをisという名前で作成
    BufferedReader br = null;
    int xsize, ysize;
    int i, j;
    se = new AudioClip[4];

    // パラメータの読み込み
    s = getParameter( "x" );
    x = ( s != null ) ? Integer.valueOf( s ).intValue() : 1;
    s = getParameter( "y" );
    y = ( s != null ) ? Integer.valueOf( s ).intValue() : 1;
    s = getParameter( "vx" );
    vx = ( s != null ) ? Integer.valueOf( s ).intValue() : 0;
    s = getParameter( "vy" );
    vy = ( s != null ) ? Integer.valueOf( s ).intValue() : 1;
    s = getParameter( "xsize" );
    xsize = ( s != null ) ? Integer.valueOf( s ).intValue() : 20;
    s = getParameter( "ysize" );
    ysize = ( s != null ) ? Integer.valueOf( s ).intValue() : 20;
    s = getParameter( "se0" );
System.out.println("K=" + getCodeBase()+"//"+s );
    if ( s != null ) se[0] = getAudioClip( getCodeBase(), s + ".au" );
    s = getParameter( "se1" );
    if ( s != null ) se[1] = getAudioClip( getCodeBase(), s + ".au" );
    s = getParameter( "se2" );
    if ( s != null ) se[2] = getAudioClip( getCodeBase(), s + ".au" );
    s = getParameter( "se3" );
    if ( s != null ) se[3] = getAudioClip( getCodeBase(), s + ".au" );
    map = new int[xsize][ysize];
    s = getParameter( "map" );
    if ( s == null ) s = "Maze";

    // ファイル名を元に、そのファイルを openStream()で読み出し可能に
    // します
    try {
      is = new URL( getDocumentBase(), s + ".map" ).openStream();
      br = new BufferedReader( new InputStreamReader( is ) ); 
    }
    catch ( MalformedURLException e ) {}
    catch ( IOException e ) {}

    for ( i = 0 ; i < ysize ; i++ ) {
      try {
	s = br.readLine(); // 1行をファイルから読みます
	k = s.toCharArray(); // 取ってきた行を配列へ入れます
      }
      catch ( IOException e ) { break; }
      for ( j = 0; j < xsize ; j++ ) {
	switch ( k[j] ) {
	case '#':        // #だったら壁
	  map[i][j] = 1;
	  break;
	case 'o':        // oだったら扉
	  map[i][j] = 2;
	  break;
	case 'x':        // xだったらゴール
	  map[i][j] = 3;
	  break;
	default:         // それ以外は通路
	  map[i][j] = 0;
	  break;
	}
      }
    }
    try {
      is.close(); // openStream()で使ったファイルは使用後、close()を必ず
      // する必要があります
    }
    catch ( IOException e ) {}
    setFont(new Font("TimesRoman",Font.BOLD,24));
    whatplay = 2; // 2番の音(スタート音)を設定しておきます

    addKeyListener( this );
    requestFocus(); // キーイベントを取得する場合はフォーカスセットする
  }

  public void paint( Graphics scr ) { // 画面描画メソッド
    int sx, sy;
    pr = 0;

    /* この下のコメントを外すと現在の位置をアプレット下に表示しま
       す
       showStatus ( String s );
       は、アプレット下に文字列s を表示します。デバッグ等に使う事が出
       来ます */
    //    showStatus( "X:" + x + " Y:" + y );

    scr.setColor( Color.black );
    /* fillRect( int x1, int y1, int x2, int y2 );
       は、現在の描画色で(x1,y1)を左上、(x2,y2)を右下にとるような長方
       形を描きその中を塗りつぶします */
    scr.fillRect( 0, 0, getSize().width, getSize().height );

    for ( pr=0; pr<10; pr++ ) {
      sx= x + vx * pr;
      sy= y + vy * pr;
      f=map[sy][sx];       // 前方の状況を調べます
      l=map[sy - vx][sx + vy]; // 左方の状況
      r=map[sy + vx][sx - vy]; // 右方の状況
      if ( f > 0 ) { // もし通路でない場合
	front( f, pr, scr );    // frontメソッドを実行します
	break;
      }
      leftline( l, pr, scr );  // leftlineメソッドを実行します
      rightline( r, pr, scr ); // rightlineメソッドを実行します
    }
    
    if ( map[y][x] == 3 ) { // ゴールだったら
      scr.setColor( Color.white );
      scr.drawString( "Goal!!!", 130, 150 );
      scr.drawString( "Congratulations!", 50, 200 );
      whatplay = 3; // 3番の音(ゴール音)を設定
    }
    // 音声配列se に音データが入っていれば play()メソッドで鳴らします
    if ( se[whatplay] != null ) se[whatplay].play();
  }

  public void update( Graphics scr ) { // repaint()メソッドから呼び出さ
                                       // れます
    /* 通常updateメソッドは画面を消去した後、paintメソッドを実行します。
       しかし、このやり方だと消去時にちらついてしまうので直接paintメソッ
       ドを実行するようにしています。このアプレットのpaintメソッドの中
       では書くたびに一度画面を塗りつぶしているのでこうしても大丈夫で
       す */
    paint( scr );
  }

  public void front( int f, int pr, Graphics scr ) { // 正面に壁がある場
                                                     // 合のメソッド
    int gx[];
    int gy[];

    gx = new int[4];
    gy = new int[4];

    gx[0] = pr * 16;
    gy[0] = pr * 16;
    gx[1] = 320 - pr * 16;
    gy[1] = gy[0];
    gx[2] = gx[1];
    gy[2] = 320 - pr * 16;
    gx[3] = gx[0];
    gy[3] = gy[2];

    switch ( f ) {
    case 1: // 通常の壁(水色)
      scr.setColor( Color.cyan );
      break;
    case 2: // 扉(黄色)
      scr.setColor( Color.yellow );
      break;
    case 3: // 出口(赤色)
      scr.setColor( Color.red );
      break;
    }
    /* fillPolygon( int xPoints[], int yPoints[], int nPoints);
       は塗りつぶされたn角形を描きます。[0]から[n-1]の配列
       xPoints,yPointsに各ポイントのX座標とY座標を入れていきます。最初
       の点と最後の点は最後に繋ぎ合わされます。 */
    scr.fillPolygon( gx, gy, 4 );
  }

  public void leftline( int l, int pr, Graphics scr ) { // 左側に壁があ
						        // る場合のメソッ
						        // ド
    int gx[];
    int gy[];

    gx = new int[4];
    gy = new int[4];

    if ( l > 0 ) {
      gx[0] = 16 + pr * 16;
      gy[0] = 16 + pr * 16;
      gx[1] = pr * 16;
      gy[1] = pr * 16;
      gx[2] = gx[1];
      gy[2] = 320 - pr * 16;
      gx[3] = gx[0];
      gy[3] = 304 - pr * 16;
      switch ( l ) {
      case 1: // 普通の壁(青色) 正面の壁と違う色にすることで立体感が増
	      // します
	scr.setColor( Color.blue );
	break;
      case 2: // 扉(黄)
	scr.setColor( Color.yellow );
	break;
      case 3: // ゴール(赤)
	scr.setColor( Color.red );
	break;
      }
    }
    else
      {
      gx[0] = pr * 16;
      gy[0] = 16 + pr * 16;
      gx[1] = 16 + pr * 16;
      gy[1] = gy[0];
      gx[2] = gx[1];
      gy[2] = 304 - pr * 16;
      gx[3] = gx[0];
      gy[3] = gy[2];
      scr.setColor( Color.cyan );
      }
    scr.fillPolygon( gx, gy, 4 );
  }

  public void rightline( int r, int pr, Graphics scr ) { // 右側に壁があ
						         // る場合のメソッ
						         // ド
    // 内容はleftline とほとんど同じ

    int gx[];
    int gy[];

    gx = new int[4];
    gy = new int[4];

    if ( r > 0 ) {
      gx[0] = 320 - pr * 16;
      gy[0] = pr * 16;
      gx[1] = 304 - pr * 16;
      gy[1] = 16 + pr * 16;
      gx[2] = gx[1];
      gy[2] = 304 - pr * 16;
      gx[3] = gx[0];
      gy[3] = 320 - pr * 16;
      switch ( r ) {
      case 1:
	scr.setColor( Color.blue );
	break;
      case 2:
	scr.setColor( Color.yellow );
	break;
      case 3:
	scr.setColor( Color.red );
      }
    }
    else
      {
      gx[0] = 304 - pr * 16;
      gy[0] = 16 + pr * 16;
      gx[1] = 320 - pr * 16;
      gy[1] = gy[0];
      gx[2] = gx[1];
      gy[2] = 304 - pr * 16;
      gx[3] = gx[0];
      gy[3] = gy[2];
      scr.setColor( Color.cyan );
      }
    scr.fillPolygon( gx, gy, 4 );
  }

  public void keyTyped( KeyEvent e ) { // キー処理メソッド

    int backup; // バックアップ用
    switch ( e.getKeyChar() ) { // 押されたキーのコードによって分別
    case e.VK_UP: // 前方移動 "↑","j","8" を使う事が出来ます
    case 'j':
    case '8':
      if ( map[y + vy][x + vx] != 1 ) { // 壁でなければ
	x += vx;
	y += vy;
	if ( map[y][x] == 2 ) { // ドアだった場合
	  x += vx; // そのままだと扉の中に入りっぱなしになってしまうので
	  y += vy; // 無理矢理もう一歩進めさせています
	  whatplay = 1; // 1番の音(ドアを開く音)を設定
	}
	else whatplay = 0; // 0番の音(歩行音)を設定
      }
      break;
    case e.VK_DOWN: // 後方移動 "↓","k","2"を使う事が出来ます
    case 'k':
    case '2':
      if ( map[y - vy][x - vx] != 1 ) {
	x -= vx;
	y -= vy;
	if ( map[y][x] == 2 ) {
	  x -= vx;
	  y -= vy;
	  whatplay = 1;
	}
	else whatplay = 0;
      }
      break;
    case e.VK_LEFT: // 左回転 "←","h","4"を使う事が出来ます
    case 'h':
    case '4':
      backup = vx;
      vx = vy;
      vy = backup * ( -1 );
      whatplay = 0;
      break;
    case 7: // 右回転 "→","l","6"を使う事が出来ます
    case e.VK_RIGHT:
    case 'l':
    case '6':
      backup = vx;
      vx = vy * ( -1 );
      vy = backup;
      whatplay = 0;
      break;
    }
    repaint();      // 再描画します
  }

  public void keyPressed( KeyEvent e ) {
  }

  public void keyReleased( KeyEvent e ) {
  }
}

