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

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

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

程序运行如图

代码如下
Minesweeper.java这个类每有变化

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

屏幕大小 170*210 大于这个屏幕的手机都可以正常显示
如果机器建值不对,请修改后编译。