Android 扫雷总结

来源:互联网 发布:linux配置中文输入法 编辑:程序博客网 时间:2024/06/06 01:54

源码参考文档:http://blog.csdn.net/umengcom/article/details/6046100

开始以为直接看源码很简单,大概思考了过程,就参考源码自己写程序,但中间出现了很多问题。终究是“纸上得来终觉浅,绝知此事要躬行”。以后一定不能手懒,要多动手。

 

源码分析:

Block类(继承自Button类):即点击时的每个小方块。主要属性包括:

public class Block extends Button {private boolean isCovered;//是否被覆盖(未被打开)的标记private boolean isMined;//是否有雷private boolean isFlagged;//是否被标记(没有标记,或标记为有雷)private boolean isQuestionMarked;//是否被标记为问号private boolean isClickable;//是否可点击(如果是空格,点击过则不可被点击,如果是雷,或者问号,则点击后仍可被点击)private int numberOfMineInSurrounding;  //如果是数字,则其周围相邻中含有雷的数目


这里主要说明一下isFlagged与isQuestionMarked的作用:当单击方块时,表示确定该方块为数字,当长按某个未曾打开的方块时,表示对该方块进行标记,isFlagged为ture:长按isFlagged为true的方块时,该方块会显示“?”,isQuestionMarked变为true,表示不确定该方块状态,当再长按isQuestionMarked为true的方块时,其恢复为初始状态。

 

这里还有两个名字很相似,很容易混淆的方法:

public void setNumberOfMinesInSurrounding(int number) { this.numberOfMineInSurrounding = number;}
public void setNumberOfMineInSurrounding(int numberOfMineInSurrounding) {this.setBackgroundResource(R.drawable.square_grey);updateNumber(numberOfMineInSurrounding);}


第一个为设置周围雷个数的函数,第二个是对方格进行背景变色即显示数字的设置,二者太相似了,极容易出错。在函数命名方面也需要注意。

 

 

主函数:

主要分为方格区显示、分布雷、为方格添加单击、长击监听事件、检验游戏是否成功或者失败、定时器显示、所需找到雷的个数显示等部分。如果哪位想自己动手写一下改程序,建议分模块逐步完善。否则后期代码的调试会非常麻烦。这里把写代码中遇到的错误贴出来:

 

在方块显示中,开始显示一直不完全,如左上所示:

后来发现问题在showMineField()的改行代码中,

mineField.addView(tableRow, new LayoutParams((blockDimension + 2 * blockPadding) * numberOfColumnsInMineField ,
     blockDimension + 2 * blockPadding));应该为:

 mineField.addView(tableRow, new TableLayout.LayoutParams((blockDimension + 2 * blockPadding) * numberOfColumnsInMineField ,
     blockDimension + 2 * blockPadding));

混淆了LayoutParams与TableLayout.LayoutParams;

BUG 2:Android   Unable to start activity CompnentInfo:java.lang.NullPointerException

以下是该BUG出现的可能原因: 

错误信息字符串:java.lang.RuntimeException: Unable to start activity ComponentInfo{com.first/com.first.Game}: java.lang.NullPointerException

一般都会在Activity  onCreate()方法里的setContentView(XXX)发生此错误,网上查阅了很多原因,大概有四种重要可能的原因:

原因一:xxx的错误,若为R.layout.main  那么应该是main.xml文件中的标签 使用错误,最常见的而且编译器不会提示的错误就是 android:name 和 android:id 两者混淆,仔细检查main.xml的标签是否全部正确

原因二:在setContentView(view)方法之后使用了requestWindowFeature()方法,并且在此错误下面会提示requestFeature必须在setContentView之前使用,只需要把requestWindowFeature()方法放在setContentView(view)方法之前就可以解决

原因三:在onCreate()方法之外,并且不属于任何一个方法体内直接给某控件findById(R.id.xx)所导致,需要在某方法内并且在setContentView(view)方法之前进行findById(R.id.xx)即可解决

原因四:在setContentView(view)之前没有对view进行实例化,只进行了声明而直接 setContentView(view) 所导致,仔细检查view是否setContentView(view)调用之前并在方法内进行实例化即可解决;

 

最后分析是有部件没有初始化导致的。

 BUG  3

:threadid=1: thread exiting with uncaught exception (group=0x40015560)
java.lang.ArithmeticException: divide by zero
at android.widget.TableLayout.mutateColumnsWidth(TableLayout.java:579)

该BUG没有解决。当时是先看的代码,就把整个程序比着写下来了,导致调试困难,后来
逐个模块进行写,调试,没有遇到该问题。隐藏的bug.希望知道的大神多多指导。

 

 

以下是源码即注释,鉴于原文及翻译已经非常清楚了,分析在这里就省略了。。。。。。

 

/***************************************************************Copyright (C) 2011 by foolstudio. All rights reserved.本源文件中代码不得用于商业用途,作者保留所有权。***************************************************************/package foolstudio.demo.app.HelloAndroid;import java.util.Random;import android.app.Activity;import android.graphics.Typeface;import android.os.Bundle;import android.os.Handler;import android.util.Log;import android.view.Gravity;import android.view.View;import android.view.View.OnClickListener;import android.view.View.OnLongClickListener;import android.widget.ImageButton;import android.widget.ImageView;import android.widget.LinearLayout;import android.widget.TableLayout;import android.widget.TableRow;import android.widget.TextView;import android.widget.Toast;import android.widget.TableRow.LayoutParams;public class HelloAndroidAct extends Activity {private TextView txtMineCount;private TextView txtTimer;private ImageButton btnSmile;private TableLayout mineField; // table layout to add mines toprivate Block blocks[][]; // blocks for mine fieldprivate int blockDimension = 24; // width of each blockprivate int blockPadding = 2; // padding between blocksprivate int numberOfRowsInMineField = 9;private int numberOfColumnsInMineField = 9;private int totalNumberOfMines = 10;// timer to keep track of time elapsedprivate Handler timer = new Handler();private int secondsPassed = 0;private boolean isTimerStarted; // check if timer already started or notprivate boolean areMinesSet; // check if mines are planted in blocksprivate boolean isGameOver;private int minesToFind; // number of mines yet to be discovered    @Overridepublic void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.mine);txtMineCount = (TextView) findViewById(R.id.MineCount);txtTimer = (TextView) findViewById(R.id.Timer);// set font style for timer and mine count to LCD styleTypeface lcdFont = Typeface.createFromAsset(getAssets(),"fonts/lcd2mono.ttf");txtMineCount.setTypeface(lcdFont);txtTimer.setTypeface(lcdFont);btnSmile = (ImageButton) findViewById(R.id.Simely);btnSmile.setOnClickListener(new OnClickListener(){@Overridepublic void onClick(View view){endCurrentGame();startNewGame();}});mineField = (TableLayout)findViewById(R.id.MineField);showDialog("Click smiley to start New Game", 2000, true, false);}            private void createMineField(){// we take one row extra row for each side// overall two extra rows and two extra columns// first and last row/column are used for calculations purposes only// x|xxxxxxxxxxxxxx|x// ------------------// x|              |x// x|              |x// ------------------// x|xxxxxxxxxxxxxx|x// the row and columns marked as x are just used to keep counts of near by minesblocks = new Block[numberOfRowsInMineField + 2][numberOfColumnsInMineField + 2];for (int row = 0; row < numberOfRowsInMineField + 2; row++){for (int column = 0; column < numberOfColumnsInMineField + 2; column++){blocks[row][column] = new Block(this);blocks[row][column].setDefaults();// pass current row and column number as final int's to event listeners// this way we can ensure that each event listener is associated to // particular instance of block onlyfinal int currentRow = row;final int currentColumn = column;// add Click Listener// this is treated as Left Mouse clickblocks[row][column].setOnClickListener(new OnClickListener(){@Overridepublic void onClick(View view){        if(!isTimerStarted){        startTimer();isTimerStarted = true;        }if(!areMinesSet){setMines(currentRow, currentColumn);areMinesSet = true;       }//如果未被标记,则打开该键及周围可以被打开的键if(!blocks[currentRow][currentColumn].isFlagged()){rippleUncover(currentRow, currentColumn);if(blocks[currentRow][currentColumn].hasMine()){loseGame(currentRow , currentColumn);}if(checkWin()){winGame();}}}});blocks[row][column].setOnLongClickListener(new OnLongClickListener(){@Overridepublic boolean onLongClick(View view){  if(!isTimerStarted){        startTimer();isTimerStarted = true;        }//Case 1:已被打开的数字 + 未被标记 + 游戏没有结束if(!blocks[currentRow][currentColumn].isCovered() && blocks[currentRow][currentColumn].getNumberOfMineInSurrounding() > 0&& !isGameOver){int nearbyFlaggedBlocks = 0;for (int previousRow = -1; previousRow < 2; previousRow++){for (int previousColumn = -1; previousColumn < 2; previousColumn++){if (blocks[currentRow + previousRow][currentColumn + previousColumn].isFlagged()){nearbyFlaggedBlocks++;}}}if(blocks[currentRow][currentColumn].getNumberOfMineInSurrounding() == nearbyFlaggedBlocks){for (int previousRow = -1; previousRow < 2; previousRow++){for (int previousColumn = -1; previousColumn < 2; previousColumn++){if(!blocks[currentRow + previousRow][currentColumn + previousColumn].isFlagged()){rippleUncover(currentRow + previousRow, currentColumn + previousColumn);if(blocks[currentRow + previousRow][currentColumn + previousColumn].hasMine()){loseGame(currentRow, currentColumn);}if(checkWin()){winGame();}}}}}return true;}//Case 2: 该按键是空格或者F或者?,进行三者间的转变//未标记也未加问号else if(blocks[currentRow][currentColumn].isClickable() && blocks[currentRow][currentColumn].isEnabled()|| blocks[currentRow][currentColumn].isFlagged()){if(!blocks[currentRow][currentColumn].isFlagged() && !blocks[currentRow][currentColumn].isQuestionMarked()){blocks[currentRow][currentColumn].setBlockAsDisabled(false);blocks[currentRow][currentColumn].setFlagged(true);blocks[currentRow][currentColumn].setFlagIcon(true);minesToFind--;updateLeftMines();}//是F状态,转向?else if(!blocks[currentRow][currentColumn].isQuestionMarked()){blocks[currentRow][currentColumn].setBlockAsDisabled(true);blocks[currentRow][currentColumn].setFlagged(false);blocks[currentRow][currentColumn].setQuestionMarked(true);blocks[currentRow][currentColumn].setQuestionMarkIcon(true);minesToFind++;updateLeftMines();}//?转向空白else{blocks[currentRow][currentColumn].setBlockAsDisabled(true);blocks[currentRow][currentColumn].setQuestionMarked(false);//blocks[currentRow][currentColumn].setText("");blocks[currentRow][currentColumn].clearAllIcons();if(blocks[currentRow][currentColumn].isFlagged()){minesToFind++;updateLeftMines();}blocks[currentRow][currentColumn].setFlagged(false);}updateLeftMines();}return true;}});}}}            private void winGame(){    stopTimer();    showDialog("Congratuations~~ You win in " + Integer.toString(this.secondsPassed) + "seconds.", 2000, false, true);        for(int row = 1; row < this.numberOfRowsInMineField + 1; ++row){        for(int column = 1; column < this.numberOfColumnsInMineField + 1; ++column){        //包含雷并已被标记        if(blocks[row][column].hasMine() && !blocks[row][column].isFlagged()){        blocks[row][column].setBlockAsDisabled(false);        blocks[row][column].setMineIcon(true);        }        //有雷但没有标记        else if(blocks[row][column].hasMine() && blocks[row][column].isFlagged()){        blocks[row][column].setFlagIcon(true);        }//包含数字或者空格        else{        blocks[row][column].openBlock();        }        }        }    }        private void updateLeftMines(){    if(this.minesToFind < 10){    this.txtMineCount.setText("00" + Integer.toString(this.minesToFind));    }    else if(this.minesToFind < 10){    this.txtMineCount.setText("0" + Integer.toString(this.minesToFind));    }    else{    this.txtMineCount.setText(Integer.toString(this.minesToFind));    }    }        private boolean checkWin(){    for(int row = 1; row < this.numberOfRowsInMineField + 1; ++row){    for(int column = 1; column < this.numberOfColumnsInMineField + 1; ++column){    //判断条件:没有雷并且已经被打开    if(!blocks[row][column].hasMine() && blocks[row][column].isCovered()){    return false;    }    }    }    return true;    }        private void loseGame(int currentRow , int currentColumn) {// TODO Auto-generated method stub    isGameOver = true;    stopTimer();    btnSmile.setBackgroundResource(R.drawable.sad);    this.isTimerStarted = false;        for(int row = 1; row < numberOfRowsInMineField + 1; row++){for(int column = 1; column < numberOfColumnsInMineField + 1; column++){blocks[row][column].setBlockAsDisabled(false);if(blocks[row][column].hasMine() && !blocks[row][column].isFlagged()){blocks[row][column].setMineIcon(false);}if(!blocks[row][column].hasMine() && blocks[row][column].isFlagged()){blocks[row][column].setFlagged(false);}if(blocks[row][column].isFlagged()){blocks[row][column].setClickable(false);}}}        blocks[currentRow][currentColumn].triggerMine();        showDialog("Game Over in"+ Integer.toString(this.secondsPassed)+"seconds.Welcome to come again~~", 2000, false, false);}        //类似于重新开始Restartprivate void endCurrentGame() {// TODO Auto-generated method stubstopTimer();isGameOver = true;areMinesSet = false;isTimerStarted = false;minesToFind = this.totalNumberOfMines;this.secondsPassed = 0;btnSmile.setBackgroundResource(R.drawable.smile);this.mineField.removeAllViews();}    private void startNewGame(){isGameOver = false;areMinesSet = false;isTimerStarted = false;minesToFind = this.totalNumberOfMines;this.createMineField();this.showMineField();}   public void rippleUncover(int rowClicked, int columnClicked){   if(!blocks[rowClicked][columnClicked].isClickable() ||   blocks[rowClicked][columnClicked].isFlagged() ){   return ;   }   blocks[rowClicked][columnClicked].openBlock();      if(blocks[rowClicked][columnClicked].getNumberOfMineInSurrounding() != 0){   return;   }      for(int prerow = -1; prerow < 2; prerow++ ){   for(int precolumn = -1; precolumn < 2; precolumn++){   if(blocks[rowClicked + prerow][columnClicked + precolumn].isCovered() && ((rowClicked + prerow) > 0 ) &&   ((rowClicked + prerow) < this.numberOfRowsInMineField + 1 ) &&   ((columnClicked + precolumn) > 0 ) &&  ((columnClicked + precolumn) < this.numberOfColumnsInMineField + 1 ) ){   rippleUncover(rowClicked + prerow, columnClicked + precolumn);   }   }   }   return ;      }      //(确保第一次点击不生成雷)  //计算出随机生成雷及非雷区的值    public void setMines(int currentRow, int currentColumn){        Random random = new Random();    int mineRow;    int mineColumn;    int index;    //此处随机数的处理有些奇怪,没有进行边界判断    for(index = 0; index < this.totalNumberOfMines; ++index){    mineRow = random.nextInt(this.numberOfRowsInMineField);    mineColumn = random.nextInt(this.numberOfColumnsInMineField);        int row = mineRow % this.numberOfRowsInMineField + 1;    int colunm = mineColumn % this.numberOfColumnsInMineField + 1;        if((row != currentRow) ||(colunm != currentColumn)){    if(blocks[row][colunm].hasMine()){    index--;    }    else{    blocks[row][colunm].plantMine();    }    }    else{    index--;    }    }        int nearbyCount ;    for(int row = 0; row < this.numberOfRowsInMineField + 2 ; ++row )    for(int column = 0 ; column < this.numberOfColumnsInMineField + 2 ; ++column){    nearbyCount = 0;    //how to deal with if the block has mine itself??????????    //如果是雷区,仍进行计算,其实大可不必,该处可以改进    //判断,如果不是边界,进行计算(边界并不算在雷区内,只是为了方便雷区数字的计算)    if((row != 0)&&(row != numberOfRowsInMineField + 1)&&(column != 0)&&(column != numberOfColumnsInMineField + 1)){    for(int prerow = -1; prerow < 2; ++prerow){    for(int precolumn = -1; precolumn < 2; ++precolumn){    //if(pre)    if(blocks[row + prerow][column + precolumn].hasMine()){    nearbyCount++;    }    }    }    blocks[row][column].setNumberOfMinesInSurrounding(nearbyCount);    }    //对边界的特殊处理,一开始就设其雷值为9(正常情况下雷最多有8个,不可能为9),然后将其打开~~    else{    blocks[row][column].setNumberOfMinesInSurrounding(9);    //blocks[row][column].openBlock();    }    }    //这里留一个未解决的问题,就是getnumberOfMineInSurrounding的值是不正确的,回来解决~~    //该问题已解决,是Block类中setNumberOfMinesInSurrounding与setNumberOfMineInSurrounding两函数    //名字太相似而导致的~~开始没有注意到二者的区别    }        private void showMineField(){for(int row = 1; row < numberOfRowsInMineField + 1; row++){TableRow tableRow = new TableRow(this);//设置布局的宽度大小tableRow.setLayoutParams(new LayoutParams((blockDimension + 2 * blockPadding) * numberOfColumnsInMineField ,blockDimension + 2 * blockPadding));for(int column = 1; column < numberOfColumnsInMineField + 1; column++){blocks[row][column].setLayoutParams(new LayoutParams(blockDimension + 2 * blockPadding  ,blockDimension + 2 * blockPadding));blocks[row][column].setPadding(blockPadding, blockPadding, blockPadding, blockPadding);tableRow.addView(blocks[row][column]);}//addView方法中仍然需要声明其大小mineField.addView(tableRow, new TableLayout.LayoutParams((blockDimension + 2 * blockPadding) * numberOfColumnsInMineField , blockDimension + 2 * blockPadding));}}    //计时器private Runnable updateTimeElasped = new Runnable(){public void run(){long currentTimeMillis = System.currentTimeMillis();secondsPassed++;if (secondsPassed < 10){txtTimer.setText("00" + Integer.toString(secondsPassed));}else if (secondsPassed < 100){txtTimer.setText("0" + Integer.toString(secondsPassed));}else{txtTimer.setText(Integer.toString(secondsPassed));}//postAtTime() :Causes the Runnable r to be added to the message queue, //to be run at a specific time given by uptimeMillis.timer.postAtTime(this, currentTimeMillis);//post this at this time//postDelayed(Runnable r, long delayMillis) //Causes the Runnable r to be added to the message queue, //to be run after the specified amount of time elapses.timer.postDelayed(updateTimeElasped, 1000);}};public void startTimer(){timer.removeCallbacks(updateTimeElasped);timer.postDelayed(updateTimeElasped, 1000);}public void stopTimer(){timer.removeCallbacks(updateTimeElasped);}private void showDialog(String message, int milliseconds, boolean useSmileImage, boolean useCoolImage){// show messageToast dialog = Toast.makeText(getApplicationContext(),message,Toast.LENGTH_LONG);dialog.setGravity(Gravity.CENTER, 0, 0);LinearLayout dialogView = (LinearLayout) dialog.getView();ImageView coolImage = new ImageView(getApplicationContext());if (useSmileImage){coolImage.setImageResource(R.drawable.smile);}else if (useCoolImage){coolImage.setImageResource(R.drawable.cool);}else{coolImage.setImageResource(R.drawable.sad);}dialogView.addView(coolImage, 0);dialog.setDuration(milliseconds);dialog.show();}};


 


 

原创粉丝点击