作者:雷神
QQ:38929568
QQ群:28048051JAVA游戏编程(满) 28047782(将满)

扫雷(2)是在扫雷(1)的基础上增加 完善了部分代码基本逻辑不变!
使得游戏更好玩了,代码两也增加到400行,比较适合初学者,可读性强,有详尽的代码注释。
数字键1标红旗,不确定,取消标记。数字键3显示/不显示 游戏信息!
方向键,数字键2468,控制光标上下左右移动!

工程文件如附件所示,地址如下: 扫雷2 

程序运行如图

Minesweeper.java 
这个类是游戏入口类,基本不做修改!而且每个游戏的这个midlet类都差不太多!

 
  
  1. ///////////////////////////////////////////////////////////////////////////////////////////////////  
  2. //  
  3. // Minesweeper.java  
  4. //  
  5. // Project: Minesweeper  
  6. // Author(s): Gao Lei  
  7. // Create: 2007-10-08  
  8. ///////////////////////////////////////////////////////////////////////////////////////////////////  
  9. import javax.microedition.midlet.*;    //j2me MIDlet程序必须继承MIDlet类,所以要引入此包  
  10. import javax.microedition.lcdui.*;    //Display这个类所在包  
  11.  
  12. ///////////////////////////////////////////////////////////////////////////////////////////////////  
  13.  
  14. public class Minesweeper extends MIDlet   
  15. ...{  
  16.     static Minesweeper s_midlet;    //MIDlet类的静态对象,方便实用 MIDlet类方法  
  17.     static Display s_display = null;//用来显示 Canvas  
  18.     static cGame s_game = null;        //Canvas类对象,主要实现游戏的类  
  19.       
  20.     public Minesweeper()  
  21.     ...{  
  22.         s_midlet = this;  
  23.     }  
  24.       
  25.     /** *//**  
  26.      * 程序开始 系统会调用这个函数  
  27.      * 也有些手机 可以把程序初始化部分放到构造函数里,这连个地方应视手机的不同而定!  
  28.      */ 
  29.     public void startApp()               
  30.     ...{  
  31.         if (s_display == null)   
  32.         ...{  
  33.             s_display = Display.getDisplay(this);//创建Display对象,参数是MIDlet类对象,也就是我们当前写的这个Minesweeper类  
  34.         }  
  35.  
  36.         if (s_game == null)   
  37.         ...{  
  38.             s_game = new cGame();                //创建 Canvas对象  
  39.             s_display.setCurrent(s_game);        //把Canvas对象设置成当前显示  
  40.         }   
  41.         else   
  42.         ...{  
  43.             s_display.setCurrent(s_game);  
  44.         }  
  45.     }  
  46.  
  47.     /** *//**  
  48.      * 程序暂停 系统会自动调用这个函数,不是所有手机都支持,  
  49.      * 手机在接到中断,如 来电,来短信时候会调用这个函数,这个函数 通常是空的!  
  50.      */ 
  51.     public void pauseApp()           
  52.     ...{  
  53.           
  54.     }  
  55.  
  56.     /** *//**  
  57.      * 程序关闭 系统会调用这个函数,如果希望关闭程序的时候保存数据,可在这个函数里添加保存数据的方法  
  58.      * 比如游戏进行中,按了关机键,程序就会调用这个函数,也可以在程序中调用这个函数来结束游戏!  
  59.      */ 
  60.     public void destroyApp(boolean unconditional)   
  61.     ...{  
  62.         notifyDestroyed();  
  63.     }  

cGame.java 这个类添加了几个方法,主要是绘制图形,和完善游戏,增加了游戏胜利和游戏难度。

 
  
  1. ////////////////////////////////////////////////////////////////////////////////  
  2. //  
  3. // cGame.java  
  4. //  
  5. // Project: Minesweeper  
  6. // Author(s): Gao Lei  
  7. // Create: 2007-10-08  
  8. ////////////////////////////////////////////////////////////////////////////////  
  9.  
  10. import java.util.Random;            //得到 随机函数  
  11. import javax.microedition.lcdui.*;    //写界面所需要的包  
  12.  
  13. ////////////////////////////////////////////////////////////////////////////////  
  14.  
  15. class cGame extends Canvas   
  16. ...{  
  17.     //游戏状态  
  18.     private static final int STATEPLAY        = 0;    //游戏中  
  19.     private static final int STATELOST        = 1;    //游戏失败  
  20.     private static final int STATEWIN        = 2;    //游戏胜利  
  21.     //格子状态  
  22.     private static final int MINE_OFF_SHOW    = 0;    //不显示格子中雷的数  
  23.     private static final int MINE_ON_SHOW    = 1;    //显示格子中雷的数  
  24.     private static final int MINE_ASK        = 9;    //设置问号  
  25.     private static final int MINE_FLAG        = 10;    //设置红旗  
  26.     private static final int MINE_GUESS_ERR    = 11;    //显示猜错了雷  
  27.     //定义键值  
  28.     private static final int KEY_UP         = 1;    //上  
  29.     private static final int KEY_DOWN         = 2;    //下  
  30.     private static final int KEY_LEFT         = 3;    //左  
  31.     private static final int KEY_RIGHT         = 4;    //右  
  32.     private static final int KEY_FIRE         = 5;    //中间确认键  
  33.  
  34.     public static Random rand;            //随机数对象  
  35.  
  36.     private int map_x     = 10;            //雷区的 行数        // 15  
  37.     private int map_y     = 10;            //雷区的 列数        // 12  
  38.     private int map_w     = 20;            //一个雷区的格子的宽度  
  39.     private int map_h     = 20;            //一个雷区的格子的高度  
  40.     private int key_x     = map_x / 2;    //游戏初始时 光标所在雷区的格子位置  
  41.     private int key_y     = map_y / 2;    //游戏初始时 光标所在雷区的格子位置  
  42.     private int mine_num= 10;            //雷区的雷数 不应该大于雷区的格子总数  
  43.     private int flagNum    = mine_num;        //剩余红旗数   
  44.     private int rightNum= 0;            //猜对的雷数  
  45.     private int[][] map;                //雷区的地图数组 >=10 为雷, <10 为周围的雷数, 0位附近没有雷  
  46.     private int[][] map_show;            //雷区的地图数组是否显示该位置的雷数//1显示//0不显示//9问号//10红旗  
  47.     private int     gameState     = STATEPLAY;    //游戏状态  
  48.     private int     s_width     = 0;            //屏幕尺寸 宽  
  49.     private int     s_height    = 0;            //屏幕尺寸 高  
  50.     private int     addMine        = 0;            //重新开始后雷数增加的个数  
  51.     private boolean isShowInfo    = false;        //是否显示游戏信息  
  52.     private String     strFlagNum     = "红旗数";  
  53.     private String     strMineNum     = "正确率";  
  54.     private String[] gameInfo     = ...{"游戏中","游戏失败 按0重新开始","游戏胜利 按0进入多雷区"};  
  55.     private Font     font         = Font.getFont( Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_LARGE );  
  56.       
  57.     cGame()  
  58.     ...{  
  59.         setFullScreenMode(true);        //设置游戏为全屏幕模式,该函数只能在支持midp2.0的手机上使用  
  60.         s_width = getWidth();            //得到屏幕尺寸     宽  
  61.         s_height= getHeight();            //得到屏幕尺寸     高  
  62.         rePlay( 0 );                    //游戏初始化//重新游戏  
  63.     }  
  64.       
  65.     /** *//**  
  66.      * 系统自动调用该绘图函数,并传入绘图设备g,通过该设备,我们可以绘制如直线,矩形快,字符串,图片等,  
  67.      */ 
  68.     public void paint(Graphics g)  
  69.     ...{  
  70.         g.setClip(00, s_width, s_height);        //设置参数描述的区域为操作区  
  71.         g.setColor(0x000000);                    //设置颜色为 黑色, 三个16进制数表示,RGB,如0x00ff00 为绿色  
  72.         g.fillRect(00, s_width, s_height);    //绘制一个实心矩形区域  
  73.         g.setColor(0xFFFFFF);                    //设置颜色为 白色  
  74.         //绘制雷区  
  75.         forint i=0; i<=map_y; i++ )    // |||    //画 map_y+1条竖线  
  76.         ...{  
  77.             g.drawLine(i*map_w, 0, i*map_w, map_h*map_x);  
  78.         }  
  79.         forint i=0; i<=map_x; i++ )     // ===    //画 map_x+1条横线  
  80.         ...{  
  81.             g.drawLine(0, i*map_h, map_y*map_w, i*map_h);  
  82.         }  
  83.  
  84.         forint i=0; i<map_x; i++ )              
  85.         ...{  
  86.             forint j=0; j<map_y; j++ )  
  87.             ...{  
  88.                 if( map_show[i][j] == MINE_ON_SHOW )    //遍历地图数组 看该位置的雷数 是否应该显示  
  89.                 ...{  
  90.                     if( map[i][j]==0 )            //周围没有雷  
  91.                     ...{  
  92.                         g.setColor(0x666666);  
  93.                         g.fillRect(j*map_h+2, i*map_w+2, map_w-3, map_h-3);  
  94.                     }  
  95.                     else if(map[i][j]<10)        //显示周围的雷数  
  96.                     ...{  
  97.                         g.setColor(0x666666);  
  98.                         g.fillRect(j*map_h+2, i*map_w+2, map_w-3, map_h-3);  
  99.                         g.setColor(0x00ff00);  
  100.                         g.drawString(""+map[i][j], j*map_h+8, i*map_w+4, g.LEFT|g.TOP);    //显示该位置的雷数  
  101.                     }  
  102.                     else                        //踩到雷了  
  103.                     ...{  
  104.                         g.setColor(0xff0000);  
  105.                         g.fillRect(j*map_h+2, i*map_w+2, map_w-3, map_h-3);  
  106. //                        g.drawString(""+map[i][j], j*map_h+5, i*map_w+4, g.LEFT|g.TOP);    //显示该位置是雷  
  107.                     }  
  108.                 }  
  109.                 else if( map_show[i][j] == MINE_FLAG )        //显示红旗  
  110.                 ...{  
  111.                     paintFlag( g, j, i );  
  112.                 }  
  113.                 else if( map_show[i][j] == MINE_ASK )        //显示问号  
  114.                 ...{  
  115.                     paintInterrogation( g, j, i );  
  116.                 }  
  117.                 else if( map_show[i][j] == MINE_GUESS_ERR )//显示猜错了  
  118.                 ...{  
  119.                     g.setColor(0x666666);  
  120.                     g.fillRect(j*map_h+2, i*map_w+2, map_w-3, map_h-3);  
  121.                     paintGuessErr( g, j, i );  
  122.                 }  
  123.             }      
  124.         }  
  125.         g.setColor(0xFF0000);                    //设置颜色 红  
  126.         g.drawRect(key_x*map_w+1, key_y*map_h+1, map_w-2, map_h-2);        //绘制一个空心矩形框//为光标  
  127.         g.drawRect(key_x*map_w+2, key_y*map_h+2, map_w-4, map_h-4);        //绘制一个空心矩形框//为光标  
  128.           
  129.         if( isShowInfo || gameState != STATEPLAY )    //如果游戏 结束  
  130.         ...{  
  131.             g.setFont( font );  
  132.             g.drawString( strFlagNum+":"+flagNum,     20, s_height-60, g.LEFT|g.TOP );    //显示剩余旗数  
  133.             g.drawString( strMineNum+":"+rightNum +"/"+ mine_num,    20, s_height-45, g.LEFT|g.TOP );    //显示正确率 猜对雷数/总雷数  
  134.             g.drawString( gameInfo[ gameState ],     20, s_height-30, g.LEFT|g.TOP );    //显示游戏状态  
  135.         }  
  136.     }  
  137.  
  138.     /** *//**  
  139.      * 系统自动调用该函数,当有键盘事件发生为按下某键,参数key为按下键的键值  
  140.      */ 
  141.     public void keyPressed(int key)  
  142.     ...{  
  143.         key = Math.abs(key);  
  144.         System.out.println("key="+key);  
  145.         //上下左右 为移动光标事件,只需要调整光标位置即可,但需要做边界判断  
  146.         switch( key )  
  147.         ...{  
  148.             case KEY_NUM2:  
  149.             case KEY_UP:  
  150.                 if( gameState != STATEPLAY )    //如果游戏没结束//结束了就不做确认键操作了      
  151.                     break;  
  152.                 else 
  153.                 ...{  
  154.                     key_y--;  
  155.                     if( key_y<0 )  
  156.                         key_y = map_x-1;  
  157.                 }  
  158.             break;  
  159.  
  160.             case KEY_NUM8:  
  161.             case KEY_DOWN:  
  162.                 if( gameState != STATEPLAY )    //如果游戏没结束//结束了就不做确认键操作了      
  163.                     break;  
  164.                 else 
  165.                 ...{  
  166.                     key_y++;  
  167.                     key_y %=map_x;  
  168.                 }  
  169.             break;  
  170.  
  171.             case KEY_NUM4:  
  172.             case KEY_LEFT:  
  173.                 if( gameState != STATEPLAY )    //如果游戏没结束//结束了就不做确认键操作了      
  174.                     break;  
  175.                 else 
  176.                 ...{  
  177.                     key_x--;  
  178.                     if( key_x<0 )      
  179.                         key_x = map_y-1;  
  180.                 }  
  181.             break;  
  182.  
  183.             case KEY_NUM6:  
  184.             case KEY_RIGHT:  
  185.                 if( gameState != STATEPLAY )    //如果游戏没结束//结束了就不做确认键操作了      
  186.                     break;  
  187.                 else 
  188.                 ...{  
  189.                     key_x++;  
  190.                     key_x %=map_y;  
  191.                 }  
  192.             break;  
  193.  
  194.             case KEY_FIRE:  
  195.             case KEY_NUM5:  
  196.                 if( gameState == STATEPLAY )    //如果游戏没结束//结束了就不做确认键操作了      
  197.                 ...{  
  198.                     if( map_show[key_y][key_x] == MINE_FLAG )  
  199.                         break;  
  200.                     showMap( key_y, key_x );    //显示该位置的雷数  
  201.                     if( map[key_y][key_x] >=10 )//如果雷数>=10 该位置是雷,  
  202.                     ...{  
  203.                         isWinGame();  
  204.                         addMine        = 0;  
  205.                         isShowInfo     = true;  
  206.                         gameState = STATELOST;    //游戏失败  
  207.                     }  
  208.                 }  
  209.             break;  
  210.  
  211.             case KEY_NUM1:                        //设置红旗//问号//取消  
  212.                 if( gameState != STATEPLAY )    //如果游戏没结束//结束了就不做确认键操作了      
  213.                     break;  
  214.                 switch( map_show[key_y][key_x] )  
  215.                 ...{  
  216.                 case MINE_OFF_SHOW:  
  217.                     map_show[key_y][key_x] = MINE_FLAG;  
  218.                     flagNum--;  
  219.                     if( flagNum == 0 )  
  220.                     ...{  
  221.                         if( isWinGame() )  
  222.                         ...{  
  223.                             addMine        = 5;  
  224.                             isShowInfo     = true;  
  225.                             gameState      = STATEWIN;  
  226.                         }  
  227.                         else 
  228.                         ...{  
  229.                             addMine        = 0;  
  230.                             isShowInfo     = true;  
  231.                             gameState      = STATELOST;  
  232.                         }  
  233.                     }  
  234.                 break;  
  235.                 case MINE_FLAG:  
  236.                     flagNum++;  
  237.                     map_show[key_y][key_x] = MINE_ASK;  
  238.                 break;  
  239.                 case MINE_ASK:  
  240.                     map_show[key_y][key_x] = MINE_OFF_SHOW;  
  241.                 break;  
  242.                 }  
  243.             break;  
  244.             case KEY_NUM3:                        //是否显示游戏信息  
  245.                 isShowInfo = !isShowInfo;  
  246.             break;  
  247.             case KEY_NUM0:                        //当按下 数字键 0  
  248.                 rePlay( addMine );                    //重新开始游戏      
  249.             break;  
  250.         }  
  251.           
  252.         /** *//**  
  253.          * 重新执行 paint() 但该函数是立刻返回,也就是说他不会等待paint()执行完毕就返回了,  
  254.          * 如果 需要 paint()执行完毕才返回,可以使用serviceRepaints(),也可以两个都是用,但  
  255.          * repaint()应该在serviceRepaints()之前.  
  256.          */ 
  257.         this.repaint();                        //本例为键盘事件驱动,刷新函数也就是每次按下键盘才作  
  258.     }  
  259.       
  260.     boolean isWinGame()  
  261.     ...{  
  262.         boolean isWin = true;  
  263.         forint i=0; i<map_x; i++ )              
  264.         ...{  
  265.             forint j=0; j<map_y; j++ )  
  266.             ...{  
  267.                 if( map_show[i][j] == MINE_FLAG )    //显示红旗  
  268.                 ...{  
  269.                     if( map[i][j] < 10 )            //地雷猜错了  
  270.                     ...{  
  271.                         map_show[i][j] = MINE_GUESS_ERR;  
  272.                         isWin = false;  
  273.                     }  
  274.                     else 
  275.                     ...{  
  276.                         rightNum ++;  
  277.                     }  
  278.                 }  
  279.             }  
  280.         }  
  281.         return isWin;                                //群不红旗都插对了 才能通关  
  282.     }  
  283.  
  284.     void paintFlag( Graphics g, int key_x, int key_y )  
  285.     ...{  
  286.         int x = key_x*map_h+9;  
  287.         int y = key_y*map_w+5;  
  288.         g.setColor( 0xFF0000 );  
  289.  
  290.         g.drawLine( x  , y   , x  , y+11 );    //    |  
  291.         g.drawLine( x-2, y+11, x+3, y+11 );    //    -  
  292.         g.drawLine( x  , y   , x+5, y+ 2 );    //    >  
  293.         g.drawLine( x+5, y+2 , x  , y+ 4 );    //    >  
  294.         x += 1;  
  295.         y += 1;  
  296.         g.drawLine( x  , y   , x  , y+11 );    //    |  
  297.         g.drawLine( x-5, y+11, x+4, y+11 );    //    -  
  298.         g.drawLine( x  , y   , x+5, y+ 2 );    //    >  
  299.         g.drawLine( x+5, y+2 , x  , y+ 4 );    //    >  
  300.     }  
  301.  
  302.     void paintInterrogation( Graphics g, int key_x, int key_y )  
  303.     ...{  
  304.         int x = key_x*map_h+8;  
  305.         int y = key_y*map_w+5;  
  306.           
  307.         g.setColor( 0xFF0000 );  
  308.         g.drawString("?", x, y, g.LEFT|g.TOP);    //    ?  
  309.     }  
  310.       
  311.     void paintGuessErr( Graphics g, int key_x, int key_y )  
  312.     ...{  
  313.         int x = key_x*map_h+8;  
  314.         int y = key_y*map_w+5;  
  315.         g.setColor( 0xFF0000 );  
  316.         g.drawString("x", x, y, g.LEFT|g.TOP);    //    x  
  317.     }  
  318.       
  319.     //该函数是一个递归函数,把当前位置设置成显示,并判断当前位置雷数是否为0个  
  320.     //如果是0个雷,那么它周围的8个格子都要再作一次showMap  
  321.     void showMap(int x, int y)  
  322.     ...{  
  323.         if( map_show[x][y] == MINE_FLAG )      
  324.             return;  
  325.         if( map_show[x][y] == MINE_ON_SHOW )  
  326.             return;  
  327.         else 
  328.             map_show[x][y] = MINE_ON_SHOW;  
  329.  
  330.         if( map[x][y] == 0 )  
  331.         ...{  
  332.             if( x-1 >= 0 )  
  333.             ...{  
  334.                 showMap( x-1, y );  
  335.                 if( y-1 >= 0)        showMap( x-1, y-1 );  
  336.                 if( y+1 < map_y)    showMap( x-1, y+1 );  
  337.             }  
  338.             if( y-1 >= 0)            showMap( x  , y-1 );  
  339.             if( y+1 < map_y)        showMap( x  , y+1 );  
  340.             if( x+1 < map_x )  
  341.             ...{  
  342.                 showMap( x+1, y );  
  343.                 if( y-1 >= 0)        showMap( x+1, y-1 );  
  344.                 if( y+1 < map_y)    showMap( x+1, y+1 );  
  345.             }  
  346.         }  
  347.     }  
  348.       
  349.     //重新 开始 游戏  
  350.     public void rePlay( int add )  
  351.     ...{  
  352.         if( add > 0 )  
  353.         ...{  
  354.             map_x     += 1;                    //雷区的 行数    // 10 - 15  
  355.             map_y     += 1;                    //雷区的 列数    // 10 - 12  
  356.             if( map_x >= 15 )    map_x = 15;  
  357.             if( map_y >= 12 )    map_y = 12;  
  358.             addMine = 0;                    //每次增加的雷数  
  359.         }  
  360.         else 
  361.             add = 0;  
  362.  
  363.         key_x         = map_x / 2;            //游戏初始时    光标所在雷区的格子位置  
  364.         key_y         = map_y / 2;            //游戏初始时    光标所在雷区的格子位置  
  365.         mine_num    += add;                    //雷区的雷数    不应该大于雷区的格子总数  
  366.  
  367.         if(mine_num >= map_x*map_y/2)  
  368.             mine_num = map_x*map_y/10;    //纠正雷数不能超过雷区的格子数  
  369.  
  370.         flagNum        = mine_num;            //剩余红旗数  
  371.         rightNum    = 0;                //猜对的雷数  
  372.         gameState    = STATEPLAY;  
  373.         map          = new int[map_x][map_y];  
  374.         map_show     = new int[map_x][map_y];  
  375.         rand        = new Random( System.currentTimeMillis() );    //用事件作随机数种子的随机数  
  376.         isShowInfo    = false;  
  377.  
  378.         //布雷  
  379.         for(int i=0; i<mine_num; i++)        //随机mine_num个雷的位置  
  380.         ...{  
  381.             int x = rand.nextInt( map_x );    //得到 随机数 雷格子的x方向位置  
  382.             int y = rand.nextInt( map_y );    //得到 随机数 雷格子的y方向位置  
  383.             if( map[x][y] >= 10)            //如果该位置已经是雷了,就要重新布雷  
  384.             ...{      
  385.                 i--;  
  386.                 continue;  
  387.             }  
  388.             map[x][y] = 10;                    //否则 将该位置 设定为雷  
  389.             //并在该雷的周围 的雷数都作+1操作  
  390.             //以下判断为 边角判断,防止访问数组越界  
  391.             if( x-1 >= 0 )                      
  392.             ...{  
  393.                 map[x-1][y  ] += 1;  
  394.                 if( y-1 >= 0)        map[x-1][y-1] += 1;  
  395.                 if( y+1 < map_y)    map[x-1][y+1] += 1;  
  396.             }  
  397.             if( y-1 >= 0)        map[x  ][y-1] += 1;  
  398.             if( y+1 < map_y)    map[x  ][y+1] += 1;  
  399.             if( x+1 < map_x )  
  400.             ...{  
  401.                 map[x+1][y  ] += 1;  
  402.                 if( y-1 >= 0)        map[x+1][y-1] += 1;  
  403.                 if( y+1 < map_y)    map[x+1][y+1] += 1;  
  404.             }  
  405.         }  
  406.     }