import java.awt.*;
import java.awt.event.*;
import java.applet.Applet;
import java.util.*;

public class abg extends Applet implements MouseListener, ActionListener
{
  int mx = 30, my = 30;  //マップの横と縦の区画数
  int mxy = mx * my;

  int mwx[] = new int[mxy];  //壁情報(区画の右側の縦の壁、右端は無効だが0にしておくこと)
  int mwy[] = new int[mxy];  //壁情報(区画の下側の横の壁、下端は無効だが0にしておくこと)
  int mwn[] = new int[mxy];  //格子点ごとの壁の数
  int ma[] = new int[mxy];  //マップ(区画ごとのエリア番号)
  int mn;  //エリア数
  int mj[];  //隣接情報
  int mc[];  //エリアごとの現在の色

  int fm[] = new int[4];

  int dx = 14, dy = 14;  //1区画の表示サイズ
  int fx = dx * mx, fy = dy * my;  //マップの表示サイズ(右と下の枠を含まない)

  Image vi;  //仮想画面のイメージオブジェクト
  Graphics vg;  //仮想画面のグラフィックオブジェクト

  Color bc = new Color(0xcc, 0xcc, 0xcc);  //背景色
  Color wc = Color.black;  //壁の色
  Color co0 = new Color(0xff, 0x66, 0xff);
  Color co1 = new Color(0x66, 0xcc, 0x66);
  Color co2 = new Color(0x33, 0xcc, 0xff);
  Color co3 = new Color(0xff, 0xcc, 0x99);
  Color co[] = {bc, co0, co1, co2, co3};  //表示色
  Color cfl = new Color(0xcc, 0xff, 0x66);  //額縁の明るい部分の色
  Color cfd = new Color(0x88, 0xaa, 0x44);  //額縁の暗い部分の色

  int ox = 6, oy = 36;  //マップの表示位置

  Button bnewgame;  //New Game
  Button bclear;  //Clear

  public void init() {
    //背景色の設定
    this.setBackground(Color.black);

    //マップ表示用の仮想画面を作る
    vi = createImage(fx + 1, fy + 1);  //仮想画面のイメージオブジェクト
    vg = vi.getGraphics();  //仮想画面のグラフィックオブジェクト

    this.addMouseListener(this);  //mouseClicked, mouseEntered, mouseExited, mousePressed, mouseReleasedを使う

    bnewgame = new Button("New Game");
    this.add(bnewgame);
    bnewgame.addActionListener(this);
    bclear = new Button("Clear");
    this.add(bclear);
    bclear.addActionListener(this);
    newgame();
  }

  public void newgame() {
    //壁情報を作る
    {
      int x, y, n;
      //すべての壁を置く
      n = 0;
      for (y = 0; y < my; y++) {
        for (x = 0; x < mx; x++) {
          mwx[n] = 1;
          mwy[n] = 1;
          mwn[n] = 4;
          n++;
        }
      }
      for (n = mx - 1; n < mxy; n += mx) {
        mwx[n] = 0;
        mwn[n] = 0;
      }
      for (n = mx * (my - 1); n < mxy; n++) {
        mwy[n] = 0;
        mwn[n] = 0;
      }
    }
    //すべての格子点の壁の数を3つにする
    {
      int x, y, n;
      int fn;
      for (y = 0; y < my - 1; y++) {
        for (x = 0; x < mx - 1; x++) {
          n = x + mx * y;  //格子点の位置
          if (mwn[n] < 4) {
            continue;
          }
          fn = 0;
          //格子点の左側の壁を消せるか調べる
          if (x == 0) {
            fm[fn] = 0;
            fn++;
          } else if (mwn[n - 1] == 4) {
            fm[fn] = 0;
            fn++;
          }
          //格子点の右側の壁を消せるか調べる
          if (x == mx - 2) {
            fm[fn] = 1;
            fn++;
          } else if (mwn[n + 1] == 4) {
            fm[fn] = 1;
            fn++;
          }
          //格子点の上側の壁を消せるか調べる
          if (y == 0) {
            fm[fn] = 2;
            fn++;
          } else if (mwn[n - mx] == 4) {
            fm[fn] = 2;
            fn++;
          }
          //格子点の下側の壁を消せるか調べる(この方法では必ず消せる)
          if (y == my - 2) {
            fm[fn] = 3;
            fn++;
          } else if (mwn[n + mx] == 4) {
            fm[fn] = 3;
            fn++;
          }
          //3つにできなければあきらめる(この方法ではあり得ない)
          if (fn == 0) {
            continue;
          }
          fn = fm[(int)(Math.floor(Math.random() * fn))];  //消す壁を決定
          if (fn == 0) {  //格子点の左側の壁を消す
            mwy[n] = 0;
            mwn[n]--;
            if (x > 0) {
              mwn[n - 1]--;
            }
          } else if (fn == 1) {  //格子点の右側の壁を消す
            mwy[n + 1] = 0;
            mwn[n]--;
            if (x < mx - 2) {
              mwn[n + 1]--;
            }
          } else if (fn == 2) {  //格子点の上側の壁を消す
            mwx[n] = 0;
            mwn[n]--;
            if (y > 0) {
              mwn[n - mx]--;
            }
          } else {  //格子点の下側の壁を消す
            mwx[n + mx] = 0;
            mwn[n]--;
            if (y < my - 2) {
              mwn[n + mx]--;
            }
          }
        }
      }
    }
    //壁が3つある格子点の間の壁を間引く
    {
      int i, n, x, y, k;
      k = (mx - 1) * my + mx * (my - 1);
      for (i = 0; i < k / 2; i++) {
        n = (int)(Math.floor(Math.random() * k));
        if (n < (mx - 1) * my) {  //縦の壁を消す
          y = n / (mx - 1);
          x = n - (mx - 1) * y;
          n = x + mx * y;  //縦の壁の位置=縦の壁の下側の格子点の位置
          if (mwx[n] == 0) {  //既に間引かれている
            continue;
          }
          if (mwn[n] < 3) {  //下側の格子点に壁が3つない
            continue;
          }
          if (y > 0) {
            if (mwn[n - mx] < 3) {  //上側の格子点に壁が3つない
              continue;
            }
          }
          mwx[n] = 0;  //縦の壁を間引く
          mwn[n]--;  //縦の壁の下側の格子点の壁の数を減らす
          if (y > 0) {
            mwn[n - mx]--;  //縦の壁の上側の格子点の壁の数を減らす
          }
        } else {  //横の壁を消す
          n -= (mx - 1) * my;
          y = n / mx;
          x = n - mx * y;
          n = x + mx * y;  //横の壁の位置=横の壁の右側の格子点の位置
          if (mwy[n] == 0) {  //既に間引かれている
            continue;
          }
          if (mwn[n] < 3) {  //右側の格子点に壁が3つない
            continue;
          }
          if (x > 0) {
            if (mwn[n - 1] < 3) {  //左側の格子点に壁が3つない
              continue;
            }
          }
          mwy[n] = 0;  //横の壁を間引く
          mwn[n]--;  //横の壁の右側の格子点の壁の数を減らす
          if (x > 0) {
            mwn[n - 1]--;  //横の壁の左側の格子点の壁の数を減らす
          }
        }
      }
    }
    //壁情報を元にエリアの形状を調べる(エリアの分割の前処理)
    {
      int n, x, y, a, b;
      n = 0;
      for (y = 0; y < my; y++) {
        for (x = 0; x < mx; x++) {
          a = n;
          if (x > 0) {
            if (mwx[n - 1] == 0) {  //左側に壁がない
              a = ma[n - 1];  //左隣の現在のエリア番号
              while (ma[a] != a) {
                a = ma[a];
              }
            }
          }
          b = n;
          if (y > 0) {
            if (mwy[n - mx] == 0) {  //上側に壁がない
              b = ma[n - mx];  //上隣のエリア番号
              while (ma[b] != b) {
                b = ma[b];
              }
            }
          }
          if (a < b) {  //オフセットが小さいほうに合わせる
            ma[b] = a;
            ma[n] = a;
          } else if (a > b) {
            ma[a] = b;
            ma[n] = b;
          } else {
            ma[n] = a;
          }
          n++;
        }
      }
    }
    //エリアごとに最小のオフセットで塗り潰す(ここでエリアの分割が完了する)
    {
      int n, a;
      for (n = 0; n < mxy; n++) {
        a = n;
        while (ma[a] != a) {
          a = ma[a];
        }
        ma[n] = a;
      }
    }
    //エリアの番号を詰める(区画ごとにエリア番号が書き込まれたマップが完成する)
    {
      int n, a;
      a = 0;  //次のエリア番号
      for (n = 0; n < mxy; n++) {
        if (ma[n] == n) {
          ma[n] = a;
          a++;
        } else {
          ma[n] = ma[ma[n]];
        }
      }
      mn = a;  //エリア数
    }
    //隣接情報を生成する
    mj = new int[mn * mn];  //隣接情報
    {
      int n, x, y, a, b;
      for (n = 0; n < mn * mn; n++) {
        mj[n] = 0;  //隣接情報を初期化する
      }
      //縦の壁に関する隣接情報
      for (y = 0; y < my; y++) {
        for (x = 0; x < mx - 1; x++) {
          n = x + mx * y;
          if (mwx[n] == 0) {  //縦の壁がない
            continue;
          }
          a = ma[n];  //壁の左側のエリア番号
          b = ma[n + 1];  //壁の右側のエリア番号
          if (a == b) {  //縦の壁の左右のエリア番号が同じとき壁を除去する
            mwx[n] = 0;
            if (y > 0) {
              mwn[n - mx]--;  //縦の壁の上側の格子点の壁の数を減らす
            }
            if (y < my - 1) {
              mwn[n]--;  //縦の壁の下側の格子点の壁の数を減らす
            }
            continue;
          }
          mj[a + mn * b] = 1;
          mj[b + mn * a] = 1;
        }
      }
      //横の壁に関する隣接情報
      for (y = 0; y < my - 1; y++) {
        for (x = 0; x < mx; x++) {
          n = x + mx * y;
          if (mwy[n] == 0) {  //横の壁がない
            continue;
          }
          a = ma[n];  //壁の上側のエリア番号
          b = ma[n + mx];  //壁の下側のエリア番号
          if (a == b) {  //横の壁の上下のエリア番号が同じとき壁を除去する
            mwy[n] = 0;
            if (x > 0) {
              mwn[n - 1]--;  //横の壁の左側の格子点の壁の数を減らす
            }
            if (x < mx - 1) {
              mwn[n]--;  //横の壁の右側の格子点の壁の数を減らす
            }
            continue;
          }
          mj[a + mn * b] = 1;
          mj[b + mn * a] = 1;
        }
      }
    }
    //エリアごとの現在の色を初期化する
    mc = new int[mn];
    clear();
  }

  //エリアごとの現在の色を初期化する
  public void clear() {
    int n;
    for (n = 0; n < mn; n++) {
      mc[n] = 0;
    }
  }

  //ちらつきをなくすためにupdate()をオーバーライドする
  //(Netscape6.1で正常に表示されなくなる)
  public void update(Graphics g) {
    paint(g);
  }

  //表示
  public void paint(Graphics g) {
    int n, x, y;
    //内部を塗り潰す
    n = 0;
    for (y = 0; y < my; y++) {
      for (x = 0; x < mx; x++) {
        vg.setColor(co[mc[ma[n]]]);
        vg.fillRect(dx * x, dy * y, dx, dy);
        n++;
      }
    }
    //枠を描く
    vg.setColor(wc);
    vg.drawLine(0, 0, fx, 0);
    vg.drawLine(0, 0, 0, fy);
    vg.drawLine(fx, 0, fx, fy);
    vg.drawLine(0, fy, fx, fy);
    //縦の壁を描く
    for (y = 0; y < my; y++) {
      for (x = 0; x < mx - 1; x++) {
        n = x + mx * y;
        if (mwx[n] != 0) {
          vg.drawLine(dx * (x + 1), dy * y, dx * (x + 1), dy * (y + 1));
        }
      }
    }
    //横の壁を描く
    for (y = 0; y < my - 1; y++) {
      for (x = 0; x < mx; x++) {
        n = x + mx * y;
        if (mwy[n] != 0) {
          vg.drawLine(dx * x, dy * (y + 1), dx * (x + 1), dy * (y + 1));
        }
      }
    }
    drawframe(g, ox, oy, fx + 1, fy + 1, 5, cfl, cfd);  //額縁を表示する
    g.drawImage(vi, ox, oy, this);  //仮想画面から実画面へ転送する
  }

/*
○○○○○○○○○○○○○○○○○○○○○○
○○○○○○○○○○○○○○○○○○○○○●
○○○○○○○○○○○○○○○○○○○○●●
○○○○○○○○○○○○○○○○○○○●●●
○○○○○○○○○○○○○○○○○○●●●●
○○○○○●●●●●●●●●●●○●●●●●
○○○○○●××××××××××○●●●●●
○○○○○●××××××××××○●●●●●
○○○○○●××××××××××○●●●●●
○○○○○●××××××××××○●●●●●
○○○○○●××××××××××○●●●●●
○○○○○●××××××××××○●●●●●
○○○○○●××××××××××○●●●●●
○○○○○●××××××××××○●●●●●
○○○○○●××××××××××○●●●●●
○○○○○●××××××××××○●●●●●
○○○○○○○○○○○○○○○○○●●●●●
○○○○○●●●●●●●●●●●●●●●●●
○○○○●●●●●●●●●●●●●●●●●●
○○○●●●●●●●●●●●●●●●●●●●
○○●●●●●●●●●●●●●●●●●●●●
○●●●●●●●●●●●●●●●●●●●●●
*/
  //額縁を表示する
  public void drawframe(Graphics g, int x, int y, int w, int h, int d, Color cl, Color cd) {
    int i;
    g.setColor(cd);
    g.drawLine(x - 1, y - 1, x + w, y - 1);
    g.drawLine(x - 1, y - 1, x - 1, y + h);
    for (i = 1; i <= d; i++) {
      g.drawLine(x + w + i, y + h + i, x - i, y + h + i);
      g.drawLine(x + w + i, y + h + i, x + w + i, y - i);
    }
    g.setColor(cl);
    g.drawLine(x + w, y + h, x + w, y - 1);
    g.drawLine(x + w, y + h, x - 1, y + h);
    for (i = 1; i <= d; i++) {
      g.drawLine(x - 1 - i, y - 1 - i, x + w + i, y - 1 - i);
      g.drawLine(x - 1 - i, y - 1 - i, x - 1 - i, y + h + i);
    }
  }

  //マウスのボタンがクリックされたとき
  public void mouseClicked(MouseEvent e) {
    ;
  }

  //マウスカーソルがアプレットの表示範囲に入ったとき
  public void mouseEntered(MouseEvent e) {
    ;
  }

  //マウスカーソルがアプレットの表示範囲から出たとき
  public void mouseExited(MouseEvent e) {
    ;
  }

  //マウスのボタンが押されたとき
  public void mousePressed(MouseEvent e) {
    Point p;  //クリックされた座標(アプレット座標)
    int x, y;  //クリックされた座標(マップ座標)
    int a;  //エリアの番号
    int c0;  //現在の色
    int c1;  //新しい色
    int i, j;
    p = e.getPoint();  //クリックされた座標(アプレット座標)
    if (p.x < ox || p.x >= ox + fx || p.y < oy || p.y >= oy + fy) {
      //マップの範囲外なので無視
      return;
    }
    x = (p.x - ox) / dx;  //クリックされた座標(マップ座標)
    y = (p.y - oy) / dy;
    a = ma[x + mx * y];  //エリア番号
    if ((e.getModifiers() & InputEvent.BUTTON1_MASK) == InputEvent.BUTTON1_MASK) {
      //左ボタンがクリックされた
      c0 = mc[a];  //現在の色(0,1〜4)
      if (c0 == 0) {  //変更を認めない。変更したいときは隣を塗ってから塗り直せばよい?
        for (i = 0; i < 4; i++) {
          c1 = ((c0 + i) & 3) + 1;  //新しい色の候補
          if (c1 == c0) {
            break;
          }
          for (j = 0; j < mn; j++) {
            if (mj[j + mn * a] != 0) {  //隣合っている
              if (mc[j] == c1) {  //隣にある色なので不可
                break;
              }
            }
          }
          if (j == mn) {  //隣にない色なので採用
            mc[a] = c1;  //新しい色
            break;
          }
        }
      }
    } else if ((e.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK) {
      //右ボタンがクリックされた
      mc[a] = 0;  //色を消す
    }
    repaint();
  }

  //マウスのボタンが離されたとき
  public void mouseReleased(MouseEvent e) {
    ;
  }

  //ボタンが押されたとき
  public void actionPerformed(ActionEvent e) {
    if (e.getSource() == bnewgame) {  //New Game
      newgame();
      repaint();
    } else if (e.getSource() == bclear) {  //Clear
      clear();
      repaint();
    }
  }
}