一:概述
我在去年写的一个小游戏,数字游戏其中有三个数字游戏。
1.数独(这篇博客讲讲这个)
2.扫雷
3.数字排序
相信大家都玩过数独吧,或者看别人玩过吧。简单介绍下规则。就是往九宫格中填数字,但是数字在九宫格类,横线方向和竖线方向都不能有重复的数字,就是这个。
看看一张图片吧:
这游戏是在SurfaceView中绘制的,其中的数字,文字,按钮都是在view中利用rect,path,drawtext绘制的。这些不是重点啊。然后其中还有长按,单击,双击等手势。但是在写这个的时候我不知道其实google已经有开放出来的api做上面的手势操作的事,傻傻的我就自己去实现了。长按,双击,就是handler.post(runnable,delay)。延迟300ms后判断手势,是长按呀还是双击,还是单击。并且双击不能相应两次单击。
二:源码
好了,我们来说说数独的事吧。
生成一个数独棋盘,首先生成一个全棋盘,然后挖掉其中某些数字就形成了数独。
生成全棋盘(所有数字都生成)我利用的时回溯法。
1.先往棋盘中随机放9个数字(位置随机,数字随机)但是必须满足数独的规则(上面已经提到)是前提啊。
2.然后从第一个格子开始填数字,判断是否是随机的那九个数字,如果不是就随机放一个数字,如果这个数字不满足就重新随机一个数字,但是保证不要和上一个数字重复不然就浪费了时间了。如果在这个格子所有数字都尝试过了都不满足就回溯到上一格修改数字。
/*随机投放复合规则的九个数字*/
private void randomSetNum(){
for(int i = 0 ; i < 9 ; i ++){
int x = getRandom()-1;
int y = getRandom()-1;
int tempNum = getRandom();
if(isContradict(x, y, tempNum)){
array[x][y].systemNum = tempNum;
}
else i--;
}
/*其他没有添加的数字 置为抹去*/
for(int i = 0 ;i < 9 ; i ++){
for(int j = 0 ; j < 9 ; j ++){
if(array[i][j].systemNum == 0)
array[i][j].flag = true;
}
}
}
2.然后从第一格子开始填数字每填一个都是随机的但是这个格子1-9都填了后都不满足数独的规则的话,那么就回溯到上一个重新取值。
isContradict()是判断是否满足数独规则的。
setArrayInfo()是判断是否可以添加这个数字,比如是否已经填过了9个数字,是否需要回溯等。
/**
* 数独求解器 根据已知部分数独格子的数独题进行求解 1.先随机抽取一些数字放入格子中如果满足要求继续下步骤
* 2.如果1-9都不满足那么回溯到上一步再重新取值(并且不会取同一个值)回溯有多种情况 3.如此往复知道穷举出合适的值便可以
*
* */
private void answerShuDu() {
for (int i = 0; i < array.length; ++i) {
for (int j = 0; j < array[i].length; ++j) {
/* 当前节点是被抹去了的 */
if (array[i][j].flag) {
/* 获取一个1-9的随机数字 */
int tempNum = getRandom();
/* 如果这个数字不重复那么就可以往“栈”中添加数据成功 成功后变判断这个数在这个位置是否满足规则 */
if (setArrayInfo(i, j, tempNum) && isContradict(i, j, tempNum)) {
/* 将位置上的数字记录下来 */
array[i][j].systemNum = tempNum;
/*将当前数字设置为正常数*/
array[i][j].flag = false;
}
else{
i = arrayPosition.get(arrayPosition.size() - 1).x ;
j = arrayPosition.get(arrayPosition.size() - 1).y ;
j--;
}
}
}
}
}
生成棋盘后开始挖洞了,我在网上查到过一篇论文专门讲如何挖洞的。就是挖洞后要验证数独的唯一性,不然挖的不好就有多个解了。
这论文对我帮助挺大的,他会根据不同难度,变换算法去挖不同的洞,这个我基本实现了。他并不是多挖洞就增加难度了。喜欢数独的应该都知道,有的洞多了反而还比较简单
不过在验证唯一性的地方我确实没做好。确实会生成多解数独。
下面是挖洞的代码。就是将需要挖去的格子的flag置为true。
private void removeSomeNumber() {
if (level == 1) {
for (int i = 0; i < 47; i++) {
int x = getRandom8();
int y = getRandom8();
if(array[x][y].flag){
/*避免挖到同一个洞*/
i--;
continue;
}
/*尝试挖洞*/
array[x][y].flag = true;
if (!checkSole(x, y)) {
/*挖洞不满足唯一性 填上*/
i--;
array[x][y].flag = false;
}
}
} else if (level == 2) {
for (int i = 0; i < 55; i++) {
int x = getRandom8();
int y = getRandom8();
if(array[x][y].flag){
/*避免挖到同一个洞*/
i--;
continue;
}
/*尝试挖洞*/
array[x][y].flag = true;
if (!checkSole(x, y)) {
/*挖洞不满足唯一性 填上*/
Log.e("挖洞中.....","i = "+i);
i--;
array[x][y].flag = false;
}
}
}
/* 间隔方式 */
else if (level == 3) {
for(int i = 0 ;i < array.length ; i ++){
if( i % 2 == 0){
/*偶数行 i 8这个洞不能挖*/
for(int j = 0 ;j < 6 ; j ++){
int tempY = getRandom8();
if(tempY == 8 || array[i][tempY].flag){j--;continue;}
array[i][tempY].flag = true;
if (!checkSole(i, tempY)) {
/*挖洞不满足唯一性 填上*/
Log.e("挖洞中.....","i = "+i);
j--;
array[i][tempY].flag = false;
}
}
}
else{
for(int j = 0 ;j < 6 ; j ++){
int tempY = getRandom8();
if(tempY == 0|| array[i][tempY].flag){j--;continue;}
array[i][tempY].flag = true;
if (!checkSole(i, tempY)) {
/*挖洞不满足唯一性 填上*/
Log.e("挖洞中.....","i = "+i);
j--;
array[i][tempY].flag = false;
}
}
}
}
/*剩余4个随机分配*/
for(int i = 0 ;i < 4 ; i ++){
int tempX = getRandom8();
int tempY = getRandom8();
if(array[tempX][tempY].flag){i--;continue;}
else{
array[tempX][tempY].flag = true;
if (!checkSole(i, tempY)) {
/*挖洞不满足唯一性 填上*/
i--;
array[tempX][tempY].flag = false;
}
}
}
}
/* 蛇形 去除60个每行至少6个剩余6个随机去除*/
else if (level == 4) {
for(int i = 0 ;i < array.length ; i++){
for(int j = 0 ; j < 6 ;j ++){
int tempY = getRandom8();
if(array[i][tempY].flag){j--;continue;}
array[i][tempY].flag = true;
if (!checkSole(i, tempY)) {
/*挖洞不满足唯一性 填上*/
j--;
array[i][tempY].flag = false;
}
}
}
for(int j = 0 ;j < 6 ; j++){
int tempY = getRandom8();
int tempX = getRandom8();
if(array[tempX][tempY].flag){j--;continue;}
array[tempX][tempY].flag = true;
if (!checkSole(tempX, tempY)) {
/*挖洞不满足唯一性 填上*/
j--;
array[tempX][tempY].flag = false;
}
}
}
/* 从左到右从上到下。 */
else if (level == 5) {
for(int i = 0 ;i < array.length ; i++){
for(int j = 0 ; j < 6 ;j ++){
int tempY = getRandom8();
if(array[i][tempY].flag){j--;continue;}
array[i][tempY].flag = true;
if (!checkSole(i, tempY)) {
/*挖洞不满足唯一性 填上*/
j--;
array[i][tempY].flag = false;
}
}
}
for(int i = 0 ;i < 8 ; i++){
int tempY = getRandom8();
int tempX = getRandom8();
if(array[tempX][tempY].flag){i--;continue;}
array[tempX][tempY].flag = true;
if (!checkSole(i, tempY)) {
/*挖洞不满足唯一性 填上*/
i--;
array[tempX][tempY].flag = false;
}
}
}else {
throw new RuntimeException("难度有问题!");
}
}
三:
好了最后讲讲其中的数据结构吧,就是每个棋盘上的点的结构。
back 供用户不确定的数据放进去 (候选数字)
userNum是用户确定的数据
systemNum是系统生成的数据
flag 是否是抹去的数字 true是抹去的。false是没有抹去的
每个格子系统都会在生成棋盘的时候生成一个数字这个数字就放在systemNum中。
挖去的格子就是让用户输入的使用flag 判断是否挖去。
挖去的格子会让用户输入,有候选数字back ,和用户确定的数字用userNum。
最后判断是否成功就是用userNum来判断用户是否完成游戏。
/**
*
* @author Administrator back 供用户不确定的数据放进去 (候选数字) userNum是用户确定的数据
* systemNum是系统生成的数据 flag 是否是抹去的数字 true是抹去的。false是没有抹去的
*
*/
public class NumNode {
private ArrayList<Integer> back = new ArrayList<Integer>();
public int systemNum = 0;
public int userNum = 0;
public boolean flag = false;
/* 添加候选数,如果候选数的多于了9个那么就不能插入数字了 */
boolean setBackNum(int num) {
for (int i = 0; i < back.size(); i++) {
if (num == back.get(i))
return false;
}
if ((int) back.size() > 9) {
return false;
}
back.add(num);
return true;
}
/*去除队列中的某个数字*/
void clearBackNum(Integer num){
back.remove(num);
}
public ArrayList getBack() {
return back;
}
}
好了,每次玩后都会将游戏进度保存到本地,保证下次玩的时候可以将数据读取出来,继续愉快的玩耍。
我是保存在本地数据库中,用 xutilslibrary.jar 做的本地数据库的操作。
这是14年写的代码。自己看着都有点晕了。
附上源码吧(三个游戏都在这里)
加个好友共同学习(不是公众号):
因为小弟水平有限,如果有写的有问题,希望指出。