Android开发实例-自动生成题库的数独(一)

转载请注明出处:http://blog.csdn.net/einarzhang/article/details/44834259

本系列文章主要介绍如何利用Android开发一个自动生成题目的数独游戏。涉及的知识和技术如下所示:

  • 挖洞算法自动生成数独题目
  • 实现自定义View用于绘制数独盘
  • 数据库的基本操作

看着市场上千篇一律的数独应用,他们大都来自于同一个开源应用,题目都是固定不变的那么100多道。我们就没有方法改变数独题目吗?经过百度搜索,终于找到了一篇自动生成数独题库的算法,感谢原作者的理论以及网络上的部分代码。算法文档题库:http://wenku.baidu.com/link?url=yA-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)) {
				// 除待挖的数字之外,还有其他的解,则返回false
				data[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<Point> blankList = new ArrayList<Point>(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<Point> 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)) {
					// 任何一个数字填充错误,则返回false
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * 获取冲突列表
	 * @return
	 */
	public ArrayList<Point> validateForList() {
		ArrayList<Point> pList = new ArrayList<Point>();
		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;
	}

}
时间: 2024-10-18 04:48:28

Android开发实例-自动生成题库的数独(一)的相关文章

Android开发之自动更换壁纸

本程序主要实现了: 1.使用AssetManager将assets目录中的文件复制到SD卡的指定位置 2.使用AlarmManager全局定时器,周期性的启动指定组件切换壁纸 3.使用SharedPreferences,将用户个性化的设置保存到手机(例如壁纸切换频率) 4.使用自定义标题栏 5.使用了GestureDetector手势检测器,允许用户滑动切屏 6.使用了overridePendingTransition,在切屏的时候有动画效果 程序运行效果图: 程序代码: ChangeWallp

新版本ADT创建Android项目无法自动生成R文件解决办法

本人使用的是ADT是Version 23.0.2,支持Android 6.0之后的系统环境,最高版本23,在创建Android项目的时候,每次创建项目选择“Compile With”低于6.0版本的时候,都无法自动生成R文件,这个时候MainActivity文件报错,反复点击项目后执行“Clean”后,错误无法解 决,按照之前不生成R文件的办法解决不了,那怎么办呢?先分析错误产生的原因. 案例参考:新版本ADT创建Android项目无法自动生成R文件解决办法 | TeachCourse

一个可以自动生成静态库,自动安装程序的Makefile

.PHONY:clean install CC=g++ CFLAGS=-Wall -g BIN=libecho.a INCLUDE=echo SRC=src OBJS=Socket.o Rio.o TcpConnection.o PollPoller.o InetAddress.o TcpServer.o Thread.o Condition.o ThreadPool.o Exception.o Timer.o TimerThread.o STARD=-std=c++0x -rdynamic $

Android开发之自动登录功能的实现

在我们平时使用的手机应用都可以实现只需要登陆一次账号后,第二次进入应用直接跳转到效果界面的效果,还有QQ的登陆框是如何记忆我们的隐身登陆,保存账号选项的呢,这些都是通过使用SharedPreferences共享参数效果实现的,而无须使用数据库来存储.以下我们直接看详细代码分析. package com.example.account.login; import java.util.HashMap; import java.util.Map; import com.android.dao.MySQ

Android开发实例之miniTwitter登录界面的实现

原文: http://www.jizhuomi.com/android/example/134.html 本文要演示的Android开发实例是如何完成一个Android中的miniTwitter登录界面,下面将分步骤讲解怎样实现图中的界面效果,让大家都能轻松的做出美观的登录界面. miniTwitter登录界面效果图 先贴上最终要完成的效果图: miniTwitter登录界面的布局分析 首先由界面图分析布局,基本可以分为三个部分,下面分别讲解每个部分. 第一部分是一个带渐变色背景的LinearL

Android开发实例之多点触控程序

智能终端设备的多点触控操作为我们带来了种种炫酷体验,这也使得很多Android开发者都对多点触控程序的开发感兴趣.实际上多点触控程序的实现并不是那么遥不可及,而是比较容易.本文就主要通过一个实例具体讲解多点触控程序的实现. 首先来了解一下Android中多点触控的原理. Android多点触控在本质上需要LCD驱动和程序本身设计上支持,目前市面上HTC.Motorola和Samsung等知名厂商只要使用电容屏触控原理的手机均可以支持多点触控Multitouch技术,对于网页缩放.手势操作上有更好

android for vs (二)visual studio android 开发实例

android for vs (一)visual studio android 开发实例 相关 vs 的 android 开发环境安装配置可以看我的前一篇文章 这里使用 vs2010 自带的实例进行开发与调试 一.新建项目 文件 -> 新建 -> 项目,我们选择Blank App(Android)项目,如下图 二.项目目录结构 1)AndroidManifest.xml 项目配置描述文件,项目名.图标.运行程序需要的权限都可以在这里声明 2)Main.axml 界面布局及元素定义文件 3)Ma

android开发常用组件和第三方库(二)

TimLiu-Android 自己总结的Android开源项目及库. github排名 https://github.com/trending, github搜索:https://github.com/search 目录 UI UI 卫星菜单 节选器 下拉刷新 模糊效果 HUD与Toast 进度条 UI其它 动画 网络相关 响应式编程 地图 数据库 图像浏览及处理 视频音频处理 测试及调试 动态更新热更新 消息推送 完整项目 插件 出名框架 其他 好的文章 收集android上开源的酷炫的交互动

解决新建Android工程时自动生成appcompat_v7

appcompat_v7是Google自己的一个兼容包,就是一个支持库,能让2.1以上全使用上4.0版本的界面. 那么有什么办法可以让项目不自动生成这个项目呢?可以这样做:在建立Android Application Project的时候,我们将Minimum Required SDK选择到Android 4.0版本或以上,就不会生成这个项目了.