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();}};
- Android 扫雷总结
- 扫雷小游戏难点总结
- Android 扫雷游戏
- android扫雷程序
- android小游戏 扫雷
- android 扫雷游戏及源代码
- 扫雷
- 扫雷
- 扫雷
- 扫雷
- 扫雷
- 扫雷
- 扫雷
- 扫雷
- 扫雷
- 扫雷
- 扫雷
- 扫雷
- linux下vim命令详解
- UESTC 1808 Eating Fish is Fun
- Portal开源框架介绍
- Orcale中的decode函数用法
- 堆和栈
- Android 扫雷总结
- hsql错误: user lacks privilege or object not found: LAST_INSERT_ID
- cmd 一键清除系统垃圾
- js自定义函数
- JXLS生成EXCEL并下载
- 开启智慧之门
- Servlet介绍-listener
- ajax 请求某个文件
- 服务器和客户端的交互方式(Socket,http协议)和各自特点适用范围