Android数字游戏之数独(自动随机生成不同难度的数独)

一:概述

我在去年写的一个小游戏,数字游戏其中有三个数字游戏。

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年写的代码。自己看着都有点晕了。

附上源码吧(三个游戏都在这里)

源码下载

加个好友共同学习(不是公众号):

这里写图片描述

因为小弟水平有限,如果有写的有问题,希望指出。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
一、数独说明:数独由九行,九列组成,又分为顺序排列的九宫。每行、每列及每宫都包含九个格,九个格中填放1到9的不重复的数字。 二、自动计算原理(三步法): 1、基础法:找出空格中唯一可填的数字。方法是,先假设某空格中可填入九个数字,然后去掉所在行、所在列、所在宫中的已有数字。余下如果是唯一一个数字,那么这个数字就是结果 2、找唯一法:例如果某行、某列或某宫中只剩一个空格,那么九个数字中缺少的那个就是结果。 3、求唯余法:对于存在多个可能值的空格,循环取其中一个作为假设值,然后反复利用方法1和方法2去测试,如果出错冲突或导致别的空格无值可填时,说明假设的值是错误的。并对别剩余未找到唯一值的空格进行同样操作,直至找到可行的一个方案。 三、自动出题,是自动求解的反向过程,先给出答案,再组合题目: 1、数独难易程度跟数独已知的数字个数有一定关系,但不是必然关系。可分为四级,根据网友“数独难度分级”的文章https://wenku.baidu.com/view/af550ed51a37f111f1855ba0.html,结果是分布在0到1之间的一系列值,值越少越容易。 2、出题时,先利用随机数往81个空格中填入不冲突的值。方法是,因为对角线的三宫中的数字互不干扰,用随机数填充这三宫,然后根据数独规则要求随机填入另外六宫。 3、这是最终结果,然后根据难易要求,随机将结果中的一定数量(可以用随机数)的方格清空。数独题已经形成。再根据网友提供的级别计算公式,计算形成的数独题的难易程度是否符合要求。(此时的数独答案不是唯一的) 4、难易程度具体计算公式是:两个空格所有可能值如果有依赖关系值为1,没依赖关系值为0。如此汇总所有空格之间的关系值为A,再除以空格个数B的18倍。即A/(18*B)。0—0.25为0级,0.25—0.5为1级,0.5—0.75为2级,0.75—1为3组。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值