本系列文章主要介绍如何利用Android开发一个自动生成题目的数独游戏。涉及的知识和技术如下所示:
挖洞算法自动生成数独题目实现自定义View用于绘制数独盘数据库的基本操作

看着市场上千篇一律的数独应用,他们大都来自于同一个开源应用,题目都是固定不变的那么100多道。我们就没有方法改变数独题目吗?经过百度搜索,终于找到了一篇自动生成数独题库的算法,感谢原作者的理论以及网络上的部分代码。算法文档题库: IBTlo2hrjqcUOMcQ39ddYx1PaOnF0wjQycQTSYKIHJ5JUK-7WCK9Tz_BvDXQcio8I22k_xu1RZkwUYlUqFTZSa-jzyxgDfY3Ga93U34u

算法思想在文档中已经描述的十分具体,我们根据算法以及网络上的部分代码封装了可以生成任意难度数独的实体Sudoku,代码如下所示:

import java.util.ArrayList;
import java.util.Random;

/**
* 采用挖洞算法实现。不同难度将会有不同的最小填充格子数和已知格子数
*
*/
public class Sudoku {
private int[][] orginData; //保存初始状态的数独
// 初始化生成数组
private int[][] sudokuData = { { 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0 } };
// 终盘数组
private int[][] resultData;
// 最小填充格子数
private int minFilled;
// 最小已知格子数
private int minKnow;
// 当前难度级别;1-2简单难度;3普通难度;4高级难度;5骨灰级难度
private int level;

private Random ran = new Random();public Sudoku() {

// this(3); // 默认为普通难度
}

public Sudoku(int level) {if (level < 0 || level > 6) {this.level = 3;} else {this.level = level;}switch (level) {case 1:case 2:int ranNum = ran.nextInt(10);if(ranNum > 4) {minKnow = 5;} else {minKnow = 4;}minFilled = 45 + ranNum;break;

// case 2:
// minFilled = 45 + ran.nextInt(5);
// minKnow = 4;
// break;
case 3:
minFilled = 31 + ran.nextInt(10);
minKnow = 3;
break;
case 4:
minFilled = 21 + ran.nextInt(10);
minKnow = 2;
break;
case 5:
minFilled = 17 + ran.nextInt(10);
minKnow = 0;
break;
default:
break;
}
genSuduku();
orginData = new int[9][9];
for(int i = 0; i < 9; i++) {
System.arraycopy(sudokuData[i], 0, orginData[i], 0, 9);
}
}

/*** 生成唯一解的数独*/
public void genSuduku() {int startX = ran.nextInt(9), startY = ran.nextInt(9); // 初始挖洞格子int orignStartX = startX, orignStartY = startY; // 暂存初始格子int filledCount = 81; // 当前已知格子数

// int curMinKnow = 9; // 当前已知行列已知格子的下限
genShuduKnow(11);
if (solve(sudokuData, true)) {
// 将终盘赋值给初始数独数组
for (int i = 0; i < 9; i++) {
System.arraycopy(resultData[i], 0, sudokuData[i], 0, 9);
}
// 实行等量交换
sudokuData = equalChange(sudokuData);
Point nextP; // 下一个挖洞位置
do {
int temMinKnow = getMinknow(sudokuData, startX, startY);
if (isOnlyAnswer(sudokuData, startX, startY)
&& temMinKnow >= minKnow) {
sudokuData[startX][startY] = 0;
filledCount–;
// curMinKnow = temMinKnow;
}
nextP = next(startX, startY);
startX = nextP.x;
startY = nextP.y;
//校验此洞是否已挖
while(sudokuData[startX][startY] == 0 && (orignStartX != startX || orignStartY != startY)) {
nextP = next(startX, startY);
startX = nextP.x;
startY = nextP.y;
}
//初级难度处理
if(level == 1 || level == 2) {
while(startX == orignStartX && startY == orignStartY) {
nextP = next(startX, startY);
startX = nextP.x;
startY = nextP.y;
}
}
} while (filledCount > minFilled
&& (orignStartX != startX || orignStartY != startY));
} else {
// 重新生成唯一解数独,直到成功为止
genSuduku();
}
}

/*** 获取下一个挖洞点* * @param x* @param y* @return*/
public Point next(int x, int y) {Point p = null;switch (level) {case 1:case 2: // 难度1、2均为随机算法p = new Point(ran.nextInt(9), ran.nextInt(9));break;case 3: // 难度3 间隔算法if (x == 8 && y == 7) {p = new Point(0, 0);} else if (x == 8 && y == 8) {p = new Point(0, 1);} else if ((x % 2 == 0 && y == 7) || (x % 2 == 1) && y == 0) {p = new Point(x + 1, y + 1);} else if ((x % 2 == 0 && y == 8) || (x % 2 == 1) && y == 1) {p = new Point(x + 1, y - 1);} else if (x % 2 == 0) {p = new Point(x, y + 2);} else if (x % 2 == 1) {p = new Point(x, y - 2);}break;case 4: // 难度4 蛇形算法p = new Point();if (x == 8 && y == 8) {p.y = 0;} else if (x % 2 == 0 && y < 8) { // 蛇形顺序,对下个位置列的求解p.y = y + 1;} else if ((x % 2 == 0 && y == 8) || (x % 2 == 1 && y == 0)) {p.y = y;} else if (x % 2 == 1 && y > 0) {p.y = y - 1;}if (x == 8 && y == 8) { // 蛇形顺序,对下个位置行的求解p.x = 0;} else if ((x % 2 == 0 && y == 8) || (x % 2 == 1) && y == 0) {p.x = x + 1;} else {p.x = x;}break;case 5: // 从左至右,从上到下p = new Point();if (y == 8) {if (x == 8) {p.x = 0;} else {p.x = x + 1;}p.y = 0;} else {p.x = x;p.y = y + 1;}default:break;}return p;
}/*** 生成指定个数的已知格子* * @param n*/
public void genShuduKnow(int n) {// 生成n个已知格子Point[] knowPonits = new Point[n];for (int i = 0; i < n; i++) {knowPonits[i] = new Point(ran.nextInt(9), ran.nextInt(9));// 检测是否重复for (int k = 0; k < i; k++) {if (knowPonits[k].equals(knowPonits[i])) {i--;break;}}}// 填充数字int num;Point p;for (int i = 0; i < n; i++) {num = 1 + ran.nextInt(9);p = knowPonits[i];sudokuData[p.x][p.y] = num;if (!validateIandJ(sudokuData, p.x, p.y)) {// 生成格子填充数字错误i--;}}}/*** 等量交换* * @param data* @return*/
public int[][] equalChange(int[][] data) {Random rand = new Random();int num1 = 1 + rand.nextInt(9);int num2 = 1 + rand.nextInt(9);for (int i = 0; i < 9; i++) {for (int j = 0; j < 9; j++) {if (data[i][j] == 1) {data[i][j] = num1;} else if (data[i][j] == num1) {data[i][j] = 1;}if (data[i][j] == 2) {data[i][j] = num2;} else if (data[i][j] == num2) {data[i][j] = 2;}}}return data;
}/*** 判断挖去i,j位置后,是否有唯一解* * @param data* @param i* @param j* @return*/
public boolean isOnlyAnswer(int[][] data, int i, int j) {int k = data[i][j]; // 待挖洞的原始数字for (int num = 1; num < 10; num++) {data[i][j] = num;if (num != k && solve(data, false)) {// 除待挖的数字之外,还有其他的解,则返回falsedata[i][j] = k;return false;}}data[i][j] = k;return true;
}/*** 删除指定位置后,新的行列下限* * @param data* @param p* @param q* @return*/
public int getMinknow(int[][] data, int p, int q) {int temp = data[p][q];int minKnow = 9;int tempKnow = 9;data[p][q] = 0;for (int i = 0; i < 9; i++) {// 行下限for (int j = 0; j < 9; j++) {if (data[i][j] == 0) {tempKnow--;if (tempKnow < minKnow) {minKnow = tempKnow;}}}tempKnow = 9;}tempKnow = 9;for (int j = 0; j < 9; j++) {// 列下限for (int i = 0; i < 9; i++) {if (data[i][j] == 0) {tempKnow--;if (tempKnow < minKnow) {minKnow = tempKnow;}}}tempKnow = 9;}data[p][q] = temp;// 返回删除后的下限return minKnow;
}/*** 判断数独是否能解* * @param data* @return*/
public boolean solve(int[][] data, boolean flag) {int blankCount = 0; // 保存空位个数int[][] tempData = new int[9][9]; // 中间数组ArrayList blankList = new ArrayList(70); // 保存各个空格坐标for (int i = 0; i < 9; i++) {for (int j = 0; j < 9; j++) {tempData[i][j] = data[i][j];if (tempData[i][j] == 0) {blankCount++;blankList.add(new Point(i, j));}}}// 校验数独合法性if (!validate(tempData)) {return false;}if (blankCount == 0) {// 玩家已经成功解出数独return true;} else if (put(tempData, 0, blankList)) {if(flag) {// 智能解出答案,供生成数独终盘使用resultData = tempData;}return true;}return false;
}/*** 在第n个空位子放入数字* * @param data* @param n* @param blankList* @return*/
public boolean put(int[][] data, int n, ArrayList blankList) {if (n < blankList.size()) {Point p = blankList.get(n);for (int i = 1; i < 10; i++) {data[p.x][p.y] = i;if (validateIandJ(data, p.x, p.y)&& put(data, n + 1, blankList)) {return true;}}data[p.x][p.y] = 0;return false;} else {return true;}
}/*** 校验x, y位置填充的数字是否可行* * @param data* @param x* @param y* @return*/
public boolean validateIandJ(int[][] data, int x, int y) {int m = 0, n = 0, p = 0, q = 0; // m,n是计数器,p,q用于确定测试点的方格位置for (m = 0; m < 9; m++) {if (m != x && data[m][y] == data[x][y]) {return false;}}for (n = 0; n < 9; n++) {if (n != y && data[x][n] == data[x][y]) {return false;}}for (p = x / 3 * 3, m = 0; m < 3; m++) {for (q = y / 3 * 3, n = 0; n < 3; n++) {if ((p + m != x || q + n != y)&& (data[p + m][q + n] == data[x][y])) {return false;}}}return true;
}/*** 校验整个数独数组的合法性* * @param data* @return*/
public boolean validate(int[][] data) {for (int i = 0; i < 9; i++) {for (int j = 0; j < 9; j++) {if (data[i][j] != 0 && !validateIandJ(data, i, j)) {// 任何一个数字填充错误,则返回falsereturn false;}}}return true;
}/*** 获取冲突列表* @return*/
public ArrayList validateForList() {ArrayList pList = new ArrayList();for (int i = 0; i < 9; i++) {for (int j = 0; j < 9; j++) {if (sudokuData[i][j] != 0 && !validateIandJ(sudokuData, i, j)) {pList.add(new Point(i, j));}}}return pList;
}public int[][] myResultData() {return resultData;
}public int[][] myInitData() {return orginData;
}public int[][] mySudoku() {return sudokuData;
}/*** 判断格子是否为已知格子* @param x* @param y* @return*/
public boolean isKnownCell(int x, int y) {return orginData[x][y] != 0;
}public void makeToInitData() {for(int i = 0; i < 9; i++) {System.arraycopy(orginData[i], 0, sudokuData[i], 0, 9);}
}public void set(int x, int y, int num) {sudokuData[x][y] = num;
}public boolean isSuccess() {for(int i = 0; i < 9; i++) {for(int j = 0; j < 9; j++) {

// if(sudokuData[i][j] == 0 || sudokuData[i][j] != resultData[i][j]) {
// return false;
// }
if(sudokuData[i][j] == 0) {
return false;
}
}
}
return validate(sudokuData);
// return true;
}

public void setOrginData(int[][] orginData) {this.orginData = orginData;
}public void setSudokuData(int[][] sudokuData) {this.sudokuData = sudokuData;
}public void setResultData(int[][] resultData) {this.resultData = resultData;
}

}

Android开发实例-自动生成题库的数独相关推荐

  1. 采用Java编写一个软件,100以内的口算题,加减运算,运算结果位于[0,100]区间内,要求自动生成题库,实现自动判分,自动生成成绩,并且有图形化CUI界面

    这是我们软件构造实验课的内容,就简单做了这么一个实验,话不多说直接上效果,是基于springboot实现的 可以轻松算出你的错题,并且整理错题(正确题目不会出现): 为了演示,只输入了一部分 关键代码 ...

  2. android getter不起作用,java - 如何在Android Studio中自动生成getter和setter

    java - 如何在Android Studio中自动生成getter和setter Android Studio中是否有一个快捷方式可以自动生成给定类中的getter和setter? 14个解决方案 ...

  3. android应用程序判断题,Android开发工程师笔试判断题

    Android开发工程师笔试判断题20例 1.一个Activity就是一个可视化的界面或者看成是控件的容器.√ 2.Intent有很长的生命周期,是没有用户界面的程序,可以保持应用在后台运行,而不会因 ...

  4. Android开发实例大全

    <Android开发实例大全> 基本信息 作者: 王东华 丛书名: Android移动开发技术丛书 出版社:电子工业出版社 ISBN:9787121173172 上架时间:2012-8-1 ...

  5. Stark 组件:快速开发神器 —— 自动生成 URL

    说道 Stark 你是不是不会想到他--Tony Stark,超级英雄钢铁侠,这也是我的偶像. 不过我们今天要开发的 Stark 组件,倒是跟他的人工智能助手 JARVIS 有些类似,是帮助我们快速开 ...

  6. 使用HTML+javascript编写了一个生成题库小工具

    因为前段时间学校要准备考试,但是没有题库只有word的试题,我本人更习惯做题不适合背题,然后我就上网找了一下生成题库的软件,没有找到我想要的,所以就自己动手写了一个 HTML代码 插入链接与图片 代码 ...

  7. java 安卓项目案例_Java - 随笔分类 - android开发实例 - 博客园

    随笔分类 - Java 摘要:1.使用标准输入串对象System.inSystem.in.read()一次只读入一个字节数据,而我们通常要取得一个字符串或一组数字,这就很不适合,需要其他方法取得这样的 ...

  8. java机试题下载_java开发机试题(总题库).pdf

    java开发机试题(总题库) 平安Java 开发机试题 一.单选题 1. 关于WORKNET,下列描述错误的是? 应用系统必须是通过UM 认证才能使用WORKNET WORKNET-ENGINE 是核 ...

  9. Android开发实例-Android平台手机新闻客户端

    Android开发实例<Android平台手机新闻客户端>是基于Android4.0及以上平台的一款新闻类手机应用,应用运行效果如下所示: Android开发实例课程主要介绍运行于Andr ...

最新文章

  1. linux 文件和打印机共享文件夹,linux服务器向windows客户端提供文件/目录及打印机共享...
  2. 30. 多线程编程之threading模块
  3. vs调用matlab复杂,关于VS调用matlab的问题,求教大神
  4. c#sdf数据库连接_如何连接并处理 sdf 数据库文件(便捷数据库处理)
  5. 找不到_笔记本找不到无线网络怎么办
  6. python工资一般多少西安-Python面试经验分享——西安贝业思数据
  7. Springboot构建Echarts数据可视化
  8. 视觉SLAM之词袋(bag of words) 模型与K-means聚类算法浅析
  9. nginx启动vue_Docker部署前端Vue
  10. js事件循环机制-宏任务微任务
  11. 用matlab制作证件照,美图秀秀证件照制作方法图文教程
  12. c语言做线性代数第六版答案,线性代数求解(C语言):
  13. (demo)springboot接口suger_整合_hbase+phoenix
  14. 360商城页面练习(html+css+js)
  15. mysql字段提取函数_MySQL 字符串截取函数
  16. 一个B端硬件产品经理的成长史
  17. photoshop 人物修容的大体步骤
  18. 4年小Java的心路历程,绝对干货分享
  19. 解决 sublimeLinter-php 的配置问题
  20. php微擎万能门店小程序_【微擎微赞模块】万能门店小程序6.8.73+小程序前端+后端...

热门文章

  1. python删除excel某行的格式_Python+Xlwings 删除Excel的行和列
  2. Python3网络爬虫实战-38、动态渲染页面抓取:Splash的使用
  3. 加餐 | Java 面试通关攻略
  4. css3基础知识总结
  5. 7个免费的云平台,快来发布你的项目吧
  6. Proxifier代理指定程序到fiddler
  7. 全国各地区PPP项目数+投资额(2016-2021)
  8. 自然语言处理之神经网络基础
  9. vue3+jsQr实现手机浏览器调用本地摄像头扫描并识别二维码
  10. Mac上的Redis客户端 G-dis