Java扫雷游戏一例

来源:互联网 发布:java 开发工具 编辑:程序博客网 时间:2024/05/17 00:14
本文介绍一个简单的扫雷游戏例子,屏幕抓图如下。

可执行的jar文件(j2sdk1.4.2_08编译打包,包括源代码):附件:jMine.jar(20K)

要解决的问题』1. 地雷,标识棋等图形的绘制;2. 游戏数据(地雷位置)的产生;3. 非地雷格子显示数字的计算;4. 游戏逻辑『包中源文件列表』 - hysun.minegame    -- ConfigDialog.java    -- FieldCell.java    -- GameFrame.java    -- GamePanel.java    -- GraphicsUtil.java
  • ConfigDialog(extends JDialog)是配置游戏数据(雷场行列数,地雷数目)的对话框,就不多说了。
  • GameFrame(extends JFrame)只是提供一个应用窗口,也不说了。
  • GraphicsUtil提供图形绘制方法。
  • FieldCell代表一个格子。
  • GamePanel(extends JComponent implements MouseListener)代表整个雷场,并且控制游戏逻辑。

GraphicsUtil

该类提供static方法,绘制游戏中各种图形,并且将格子大小设成32x32。详情如下表所列:

未知区域
[蓝色区域]
    ....    public static Color ukcolor = new Color(99, 130, 191);    ....    public static void drawUnknown(Graphics g, int x, int y) {        g.setColor(ukcolor);        g.fillRect(x, y, 32, 32);    }
地雷
    ....    public static Color mbcolor = new Color(90, 90, 90);    ....    public static void drawMine(Graphics g, int x, int y) {        g.clearRect(x, y, 32, 32);        g.setColor(mbcolor);        g.fillOval(x+5, y+9, 21, 19);        g.setColor(Color.black);        g.fillRect(x+11, y+5, 10, 6);    }
地雷标识旗
[小红旗]
    ....    public static void drawFlag(Graphics g, int x, int y) {        g.clearRect(x, y, 32, 32);        g.setColor(Color.red);        g.fillRect(x+8, y+8, 16, 10);        g.setColor(Color.black);        g.drawLine(x+8, y+8, x+8, y+24);        g.drawLine(x+9, y+8, x+9, y+24);    }
非地雷格数字(0-8)
[不同数字使用不同颜色]
    ....    public static Color[] colorreg = new Color[] {        null,                 // 0        Color.blue,           // 1        Color.green.darker(), // 2        Color.red,            // 3        Color.blue.darker(),  // 4        Color.MAGENTA,        // 5        Color.CYAN.darker(),  // 6        Color.BLACK,          // 7        Color.orange.darker() // 8    };    ....    public static Font numfont = new Font("Verdana", Font.BOLD, 18);    ....    public static void drawNumber(Graphics g, int x, int y, int i) {        g.clearRect(x, y, 32, 32);        if (i == 0)            return;        g.setColor(colorreg[i]);        g.setFont(numfont);        FontMetrics fm = g.getFontMetrics();        String s = String.valueOf(i);        int sx = (32 - fm.stringWidth(s)) / 2;        int sy = (32 - fm.getHeight()) / 2 + fm.getAscent();        g.drawString(s, x+sx, y+sy);    }
疑问标识旗
[小蓝旗,带问号]
    ....    public static Font qnmfont = new Font("Verdana", Font.PLAIN, 10);    ....    public static void drawDoubt(Graphics g, int x, int y) {        g.clearRect(x, y, 32, 32);        g.setColor(colorreg[4]);        g.fillRect(x+8, y+8, 16, 10);        g.setColor(Color.black);        g.drawLine(x+8, y+8, x+8, y+24);        g.drawLine(x+9, y+8, x+9, y+24);        g.setColor(Color.yellow);        g.setFont(qnmfont);        FontMetrics fm = g.getFontMetrics();        String s = "?";        int sx = (14 - fm.stringWidth(s)) / 2;        int sy = (10 - fm.getHeight()) / 2 + fm.getAscent();        g.drawString(s, x+sx+10, y+sy+8);    }
叉叉
[game over时标柱错误的判断]
    ....    public static void drawCross(Graphics g, int x, int y) {        g.setColor(Color.black);        g.drawLine(x+2, y+2, x+28, y+28);        g.drawLine(x+2, y+3, x+28, y+29);        g.drawLine(x+3, y+2, x+29, y+28);        g.drawLine(x+2, y+28, x+28, y+2);        g.drawLine(x+2, y+27, x+28, y+1);        g.drawLine(x+3, y+28, x+29, y+2);    }
好了,问题1圆满解决。

FieldCell

该类的关键在于格子相关的属性变量,如下表所列:

变量详情int state代表该格子目前所处状态,取值范围是该类所定义的几个常数:UNKNOWN(该格子目前还是未知区域), FLAGGED(已经被标识为地雷), DOUBTED(被怀疑为地雷), REVEALED(已经被挖开), WRONG_F(game over时错误标识为地雷), WRONG_D(game over时错误怀疑为地雷)boolean isMine顾名思义,表明该格子是否埋有地雷int number只有当该格子没有地雷时,该变量才被用到,标识该格子周围的地雷数目,数目0-8int gHintGraphics Hint,UI利用该信息为该格子画出适当的图形,每当格子状态改变时,gHint的值将根据以上三个变量做出相应的调整。gHint的数值和实际图形的映射可以参看源代码的注释。

该类对上述变量进行操作(get/set)的方法除外,还有一个方法public void draw(Graphics g, int x, int y)。此方法根据gHint的值利用GraphicsUtil提供的方法对自身的格子进行绘画,会被GamePanel调用到。

GamePanel

最后看看游戏的老大吧[:)]。该类中有一些游戏状态显示的代码,主要是根JLabel,JButton等相关的,就略去不提了。

------
GamePanel里面有一个2D的数组:FieldCell[][] cells。问题2和3是关于游戏前数据的初始化问题,其代码包含在public void setGameParam(int mineNum, int r, int c)方法里面。

地雷数据产生的原理很简单,不断随机产生0到总格子数之间的一个数,只到不重复的数目达到所需地雷数目。如下:

    ....    int totalNum = r * c;    cells = new FieldCell[r][c];    for (int i = 0; i < r; i++) {        for (int j = 0; j < c; j++) {            cells[i][j] = new FieldCell();        }    }            int count = 0;    while (count < mineNum) {        int s = (int) (Math.random() * totalNum);        FieldCell fc = cells[s/c][s%c];        if (!fc.isMine()) {            fc.setMine(true);            count++;        }    }

相邻地雷数目的计算就要一个格子一个格子的过一边了。原理也很简单,每个格子有8个相邻的格子(处于边界的格子除外),每个格子都检查一下是不是地雷。如下:

    ....    for (int i = 0; i < r; i++) {        for (int j = 0; j < c; j++) {            if (!cells[i][j].isMine()) {                int num = 0;                if (i-1 >= 0) {                    if (j-1 >= 0 && cells[i-1][j-1].isMine())                        num++;                    if (cells[i-1][j].isMine())                        num++;                    if (j+1 < c && cells[i-1][j+1].isMine())                        num++;                }                { // i                    if (j-1 >= 0 && cells[i][j-1].isMine())                        num++;                    if (cells[i][j].isMine())                        num++;                    if (j+1 < c && cells[i][j+1].isMine())                        num++;                }                if (i+1 < r) { // i+1                    if (j-1 >= 0 && cells[i+1][j-1].isMine())                        num++;                    if (cells[i+1][j].isMine())                        num++;                    if (j+1 < c && cells[i+1][j+1].isMine())                        num++;                }                cells[i][j].setNumber(num);            } // end Non-Mine cell if        } // end for on j    } // end for on i

至此,问题2,3也得到解决。另外需要提到的是GamePanel是一个JComponent的子类,它的图形是通过override下面这个方法实现的:

    ....    protected void paintComponent(Graphics g) {        g.setColor(Color.lightGray);        g.fillRect(0, 0, w, h);        for (int i = 0; i < r; i++) {            for (int j = 0; j < c; j++) {                //格子大小是32×32,这里用34就在每两个格子间留下2的空隙                cells[i][j].draw(g, j*34, i*34);            }        }    }

代码中的w, h为该面板的长,宽,根据格子数目设置(每两个格子中间留有一段空隙)。上面讲到FieldCell类的draw方法就是在这里被调用的。

------
游戏逻辑的解决是由对鼠标事件的处理完成的。GamePanel实现了MouseListener这个接口,不过这里只用到了mouseClicked这个方法。由于实际代码中牵涉到很多其他更新用户界面的方法,为求简练,这里将用pseudo code来解释:

    public void mouseClicked(MouseEvent e) {        根据鼠标事件记录的位置找出相应的格子;        if (该格子已经被挖开: FieldCell.REVEALED)            return;        if (左键点击) {            if (被插了小红旗) //这是用来防止玩家误操作的                return;            if (踩到地雷了) {                game over; //鼠标事件停止响应                显示所有未挖出地雷,并且用叉叉标出错误的红旗和蓝旗;            } else { //挖地雷,标柱蓝旗的格子可以挖开                调用一个叫reveal(int i, int j)方法。                 // reveal(int int) 是个递归的方法,首先将自己挖开,                // 然后如果自己是个数字为0的格子,对相邻的8个格子调用reveal方法。                // reveal(int int)每挖开一个格子,挖开格子计数加加。                查看是否满足胜利条件(被挖开格子数+被插小红旗的格子数=总格子数);                if (胜利)                    恭喜玩家,停止鼠标事件响应;            }        } else if (右键点击) {  //右键用来控制插旗子            if (格子状态==FieldCell.UNKNOWN) {                if (插的小红旗数目 < 总地雷数) {                    给格子插上红旗(设置状态);                    红旗计数加加;                    查看是否胜利(同上);                } else {  //sorry, 你的红旗用完了,改用蓝旗吧。                    给格子插上蓝旗(设置状态);                }            } else if (格子状态==FieldCell.FLAGGED) {  //红旗飘扬                拔掉红旗,换成蓝旗;                红旗计数减减;            } else if (格子状态==FieldCell.DOUBTED) {  //蓝旗招摇                回归未知区域;            }        }        调用repaint()将雷场重画一边;//这个很关键,不然游戏对于玩家的操作将“无动于衷”。    }

好了,问题4也解决了,大功告成!

原创粉丝点击