myまいんすいーぱー

戻る



 Windowsにも標準添付されている超有名ゲームを自分でJavaで作ってみました。
 (やはり最終目標はこれか?(笑) はやくPCMの鳴らし方とかも覚えないと・・・)

 Windows標準添付のは、「すでに開いて数字が表示されているパネルと周囲のマークの数が同じときマークの付いていないパネルを一気にあける機能」(私はそれをクイックオープンと呼んでいますが)はマウスの左右ボタン同時押しで起動されますが、Javaでそれをやるのはちょっと面倒臭そうにおもえたので、ちょっとやり方を変えています。クイックオープン可能な場所でマウスの右ボタンを押すと、「QOpen」と書かれたポップアップメニューが出るので、それを左クリックする仕様に変えています。

 なぜ面倒と思ったかというと、Javaのマウスリスナーはそのままでは左右ボタン同時押しを検出してくれないからなんです。左ボタンだけ押すとボタン1が押された、というイベントが発生し、右ボタンだけ押すとボタン3が押されたというイベントが発生するようになっています。
 で、私はてっきり左右ボタンを同時に押すとボタン2が押されたというイベントが発生するのかな、と期待しておりました。ところが、実際にはボタン1とボタン3が押されたイベントがそれぞれ独立に発生するのですね。
 ボタン同時押しを検出するには、ボタン1とボタン3両方のmousePressedとmouseReleasedを監視して、同時押しされていることをプログラマーが検出しなきゃいけないようで、そんなんだったらポップアップメニュー出したほうが簡単かな、と思ったからです。

 参考までに、ボタン番号を調べるJavaアプリケーション



・実行中の画面

起動直後(左)とゲーム中(右)


開けていないパネルを右クリックするとマークを付けることができます。


数字と周囲のマークの数が同じとき、数字を右クリックすると、こういうポップアップメニューが出ます。


成功したとき(左)と失敗したとき(右)


・ソースプログラム(ファイル名 myminesw.java)

今回もソースだけ公開します。各自でコンパイルして実行してください。拡張for文などを使っているので、JDK1.5.x以上じゃないとコンパイルできません。
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;

public class myminesw {
  private JFrame frm;
  private JPanel grpPnl;
  private JPanel statPnl;
  private JLabel statLbl;
  private JPopupMenu pMenu;

  private BufferedImage img;

  private final int cellWidth=12;
  private final int cellHeight=12;
  private final int chartWidth=20;
  private final int chartHeight=16;
  private final int mines=chartWidth*chartHeight/10;

  private final String statInit="Initializing...";
  private final String statRdy="Ready.";
  private final String statSuc="Success!!";
  private final String statFail="Fail...";

  private LinkedList<Point> que;
  private int cellLeft;
  private int qOpenX;
  private int qOpenY;

  private class Cell {
    int x;
    int y;
    int count;
    boolean mine;
    boolean opened;
    boolean marked;

    private Cell(int x,int y) {
      this.x=x;
      this.y=y;
      this.count=0;
      this.mine=false;
      this.opened=false;
      this.marked=false;
    }

    private void setMine(boolean t) {
      this.mine=t;
      if(!t) {
        this.count=0;
        this.count=0;
        this.opened=false;
        this.marked=false;
      }
    }

    private void incCount() {
      this.count++;
    }

    private void drawSelf() {
      int gx = x*cellWidth;
      int gy = y*cellHeight;
      Graphics2D g = img.createGraphics();
      g.setFont(new Font("Monospaced",Font.PLAIN,10));

      if(this.opened) {
        g.setColor(Color.WHITE);
        g.fillRect(gx,gy,cellWidth,cellHeight);
        g.setColor(Color.BLACK);
        g.setBackground(Color.WHITE);
        if(this.mine) {
          g.drawString("*",gx+4,gy+10);
        } else {
          if(this.count>0) {
            g.drawString(Integer.toString(this.count),gx+4,gy+10);
          }
        }
        g.setColor(Color.GRAY);
        g.drawRect(gx,gy,cellWidth-1,cellHeight-1);
      } else {
        g.setColor(Color.GRAY);
        g.fillRect(gx,gy,cellWidth,cellHeight);
        g.setColor(Color.WHITE);
        g.drawLine(gx,gy,gx+cellWidth-1,gy);
        g.drawLine(gx,gy,gx,gy+cellHeight-2);
        g.setColor(Color.BLACK);
        g.drawLine(gx+cellWidth-1,gy+1,gx+cellWidth-1,gy+cellHeight-1);
        g.drawLine(gx,gy+cellHeight-1,gx+cellWidth-1,gy+cellHeight-1);

        if(this.marked) {
          g.drawLine(gx+3,gy+2,gx+3,gy+cellHeight-2);
          g.drawLine(gx+3,gy+2,gx+cellWidth-3,gy+2+(cellHeight-4)/4);
          g.drawLine(gx+3,gy+2+(cellHeight-4)/2,gx+cellWidth-3,gy+2+(cellHeight-4)/4);
        }
      }
      grpPnl.repaint();
    }

    private void setOpen(boolean t) {
      this.opened = t;
      this.drawSelf();
    }

    private void setMark(boolean t) {
      this.marked=t;
      this.drawSelf();
    }

    private void toOpen() {
      if(! this.opened) {
        this.opened = true;
        this.drawSelf();

        if(this.mine) {
          allOpen();
          statLbl.setText(statFail);
          statPnl.setBackground(Color.RED);
        } else {
          cellLeft--;
          if(cellLeft==0) {
            allOpen();
            statLbl.setText(statSuc);
            statPnl.setBackground(Color.GREEN);
          } else {
            if(this.count==0) {
              ArrayList<Point> a = this.neighbor();
              for(Point p : a) {
                if(! cells[p.y][p.x].opened) que.add(p);
              }
            }
          }
        }
      }
    }

    private ArrayList<Point> neighbor() {
      ArrayList<Point> r = new ArrayList<Point>(8);
      if(this.y>0) {
        if(this.x>0) r.add(new Point(x-1,y-1));
        r.add(new Point(x,y-1));
        if(this.x<chartWidth-1) r.add(new Point(x+1,y-1));
      }
      if(this.x>0) r.add(new Point(x-1,y));
      if(this.x<chartWidth-1) r.add(new Point(x+1,y));
      if(this.y<chartHeight-1) {
        if(this.x>0) r.add(new Point(x-1,y+1));
        r.add(new Point(x,y+1));
        if(this.x<chartWidth-1) r.add(new Point(x+1,y+1));
      }
      return r;
    }
  }

  private Cell[][] cells = new Cell[chartHeight][chartWidth];

  private void allOpen() {
    int i,j;
    for(i=0;i<chartHeight;i++) {
      for(j=0;j<chartWidth;j++) {
        cells[i][j].setOpen(true);
      }
    }
  }

  MouseListener pLsn = new MouseAdapter() {
    public void mousePressed(MouseEvent e) {
      if(e.getButton()==MouseEvent.BUTTON1) {
        if((qOpenX>=0) && (qOpenY>=0)) {
          ArrayList<Point> a = cells[qOpenY][qOpenX].neighbor();
          for(Point p : a) {
           if((! cells[p.y][p.x].opened) && (! cells[p.y][p.x].marked))
              cells[p.y][p.x].toOpen();
          }
        }
      }
    }
  };

  MouseListener mLsn = new MouseAdapter() {
    public void mousePressed(MouseEvent e) {
      int gx=e.getX();
      int gy=e.getY();
      int ix=gx/cellWidth;
      if(ix>chartWidth-1) ix=chartWidth-1;
      int iy=gy/cellHeight;
      if(iy>chartHeight-1) iy=chartHeight-1;

      if(statLbl.getText()==statRdy) {
        if(pMenu.isVisible()) {
          pMenu.setVisible(false);
          qOpenX=-1;
          qOpenY=-1;
        } else {
          if(e.getButton()==MouseEvent.BUTTON3) {
            if((cells[iy][ix].opened) && (cells[iy][ix].count>0)) {
              int n=0;
              ArrayList<Point> a = cells[iy][ix].neighbor();
              for(Point p : a) {
                if(cells[p.y][p.x].marked) n++;
              }
              if(cells[iy][ix].count==n) {
                qOpenX=ix;
                qOpenY=iy;
                pMenu.show(grpPnl,gx,gy);
              }
            } else {
              cells[iy][ix].setMark(! cells[iy][ix].marked);
            }
          } else {
            if(e.getButton()==MouseEvent.BUTTON1) {
              if(! cells[iy][ix].opened) {
                cells[iy][ix].toOpen();
              }
            }
          }
        }
      } else {
        if((statLbl.getText()==statSuc) || (statLbl.getText()==statFail)) {
          statLbl.setText(statInit);
          statPnl.setBackground(Color.GRAY);
        }
      }
    }
  };

  private void doProg() {
    int i,j,n;

    for(i=0;i<chartHeight;i++) {
      for(j=0;j<chartWidth;j++) {
        cells[i][j] = new Cell(j,i);
      }
    }

    que = new LinkedList<Point>();

    grpPnl.addMouseListener(mLsn);

    while(true) {
      if(statLbl.getText()==statInit) {
        cellLeft=chartWidth*chartHeight;

        for(i=0;i<chartHeight;i++) {
          for(j=0;j<chartWidth;j++) {
            cells[i][j].setMine(false);
            cells[i][j].setOpen(false);
          }
        }

        que.clear();
        qOpenX=-1;
        qOpenY=-1;

        n=mines;
        while(n>0) {
          i=(int)(Math.random()*chartHeight);
          j=(int)(Math.random()*chartWidth);

          if(cells[i][j].mine) continue;
          cells[i][j].setMine(true);
          n--;
          cellLeft--;

          ArrayList<Point> a = cells[i][j].neighbor();
          for(Point p : a) {
            cells[p.y][p.x].incCount();
          }
        }

        statLbl.setText(statRdy);
        statPnl.setBackground(Color.WHITE);
      } else if(statLbl.getText()==statRdy) {
        while(que.size()>0) {
          Point p = que.removeFirst();
          if(p==null) continue;
          if(! cells[p.y][p.x].opened) cells[p.y][p.x].toOpen();
          if(statLbl.getText()!=statRdy) break;
        }
      } else {
      }
    }
  }

  private void makeFrame() {
    JFrame.setDefaultLookAndFeelDecorated(true);
    frm = new JFrame("myまいんすいーぱー");
    frm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    img = new BufferedImage(chartWidth*cellWidth,chartHeight*cellHeight,
                            BufferedImage.TYPE_INT_RGB);

    JMenuItem pItmQop = new JMenuItem("Q.Open");
    pItmQop.addMouseListener(pLsn);
    pMenu = new JPopupMenu();
    pMenu.add(pItmQop);

    statPnl = new JPanel();
    statPnl.setBorder(BorderFactory.createLineBorder(Color.black));
    statLbl = new JLabel(statInit);
    statPnl.add(statLbl);
    
    grpPnl = new JPanel() {
      public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(img,0,0,this);
      }
    };

    grpPnl.setPreferredSize(new Dimension(chartWidth*cellWidth,chartHeight*cellHeight));

    frm.getContentPane().add(grpPnl,BorderLayout.CENTER);
    frm.getContentPane().add(statPnl,BorderLayout.SOUTH);
    frm.pack();
    frm.setVisible(true);
  }

  public static void main(final String[] args) {
    myminesw myProg = new myminesw();
    myProg.makeFrame();
    myProg.doProg();
  }
}


<チラシの裏>
 いままで、仕事ではアセンブラやC言語、趣味でもDelphiばかりでプログラムを組んできたので、こんな風にやたらnewでポコポコとオブジェクト作っていいのか、と不安になるのですが、ガベージコレクタが何とかしてくれると信じてnewしまくっています。

 それはそれとして、JDK1.5.xから使えるようになった拡張for文って使えますね。1.4.xまでの「イテレータ」って、配列の添え字使ってアクセスするのと比べてもほとんど記述量が減らず、使う気がしなかったのですが、拡張for文使うと劇的にすっきりします。
</チラシの裏>