创建一个数独类 DrawShuduku,该类主要作用是生成数独且验证填写值是否正确
声明一个二维数组,9行9列
private final int ROWS=9; //九宫格行数
private final int COLS=9; //九宫格列数
private int[][] currentArray;
private int[][] resultArray;
采用回溯方法,给每个小方格随机填写1-9(每一小方格都尝试填写),最终输出一个完整的数独二维数组,这个是本次的核心代码。
定义一个方法backTrackShuduku(int n),n从0开始,也就是第一行第一列(第一个小方格)
每个小方格都会尝试填写1-9的数字(随机填写),每个小方格都尝试9次(foreach循环),如果验证该数字不合法,则把该数字剔除,继续执行foreach下一个,
尝试填写其他数字,满足就填写该值 并 执行 backTrackShuduku(n+1),不满足就剔除该数字,以此类推填写后面的空格。
注意:不管所填写的数字是否合法,都需要把该foreach执行完。
n=81时,即所有空格都填满,则验证该数独合法性(数字是否有重复);如果是一个完整且合法的数独,则拷贝到另一个二维数组。
因为,此时,递归函数还没有执行完,会继续尝试填写其他数字,因为每一个小方格都会填写1-9共九次
/**
* 采用回溯方法,给每个单元格尝试1-9填值
* @param n 目前需要填写的第n个单元格
*/
private void backTrackShuduku(int n){
if(n==81){
//如果都已经填满,则校验填充的值是否都正确
for(int row=0;row<ROWS;row++){
for(int col=0;col<COLS;col++){
int value=currentArray[row][col];
if(value==0 || !isValid(row,col,value,currentArray)){
return;
}
}
}
resultArray=new int[ROWS][COLS];
for (int row = 0; row < ROWS; row++) {
for (int col = 0; col < COLS; col++) {
resultArray[row][col] = currentArray[row][col];
}
}
}
if (resultArray != null) {
return; // 若已经找到,则后面递归直接结束
}
// 获取n空格在Y方向第几个
int row = n / ROWS;
// 获取n空格在X方向第几个
int col = n % COLS;
// 判断该单元格是否有填充值
if (currentArray[row][col] > 0) {
backTrackShuduku(n + 1);
return; // 如果已经填充,则填充下一个单元格
}
// 如果该方格需要填充值,则从1-9随机赋值
ArrayList<Integer> randomList = new ArrayList<Integer>();
for (int i = 1; i <= ROWS; i++) {
randomList.add(i);
}
for (int i = 0; i < ROWS; i++) {
Random rand = new Random();
int index = rand.nextInt(randomList.size());
int value = randomList.get(index);
currentArray[row][col] = value;
randomList.remove(index);
if (isValid(row, col, value, currentArray)) {
// 如果该值满足条件,则填充下一个单元格
backTrackShuduku(n + 1);
}
}
// 如果9个数字都不满足,则清空该单元格的值,等待其他递归进来,继续填充
// 注意每个单元格都会尝试填充1-9个数字,每个数字都会产生一个递归算法
currentArray[row][col] = 0;
}
验证所填写的值是否合法,代码如下:
/**
*
* @param row 当前第几行
* @param col 当前第几列
* @param value 填充的值(1-9)
* @param array 验证的二维数组
* @return 如果对应行和对应列、以及对应小区域9个方格,值不重复,返回true,否则返回false
*/
public boolean isValid(int row, int col, int value, int[][] array) {
// 不能跟该列的其他单元格重复
for (int i = 0; i < COLS; i++) {
if (i == row) {
continue;// 排除它自己
}
if (array[i][col] == value) {
return false;
}
}
// 不能跟该行其他单元格值相同
for (int i = 0; i < ROWS; i++) {
if (i == col) {
continue;
}
if (array[row][i] == value) {
return false;
}
}
// 跟小区域其他单元格值不能重复
int x = (row / 3) * 3;
int y = (col / 3) * 3;
for (int i = x; i < x + 3; i++) {
for (int j = y; j < y + 3; j++) {
if (i == row && j == col) {
continue;
}
if (array[i][j] == value) {
return false;
}
}
}
return true;
}
生成后的二维数组,随机清空n个小方格的值
/**
* 获取一个完整的数独
* @return
*/
private int[][] getSudokuArray() {
currentArray = new int[ROWS][COLS];
resultArray = null;
for (int i = 0; i < 3; i++) {
// 避免回溯完都没有把所有单元格填完 则重新再次开始回溯 限制3次
// 一般都可一次回溯就可以把空格填完
backTrackShuduku(0);
if (resultArray != null) {
return resultArray;
}
}
return null;
}
/**
* 生成一个完整的数独 并 随机清空n个单元格
* @param spaceNum 空单元格个数
* @return 随机生成一个数独
*/
public int[][] setSudokuArray(int spaceNum) {
int[][] result = getSudokuArray();
if (result == null) {
return null;
}
ArrayList<Integer> randomList = new ArrayList<Integer>();
for (int row = 0; row < ROWS; row++) {
for (int col = 0; col < COLS; col++) {
randomList.add(row * ROWS + col);
}
}
Random rand = new Random();
for (int i = 0; i < spaceNum; i++) {
int index = rand.nextInt(randomList.size());
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
int num = randomList.get(index);
result[num / ROWS][num % COLS] = 0;
randomList.remove(index);
}
return result;
}
创建数独自定义控件 ShudukuView
绘制9行9列,81个小方格,并绘制文本
@Override
protected void onDraw(Canvas canvas) {
//绘制背景色
Paint background_paint = new Paint();
background_paint.setColor(getResources().getColor(R.color.shuduku_background, null));
canvas.drawRect(0, 0, getWidth(), getHeight(), background_paint);
drawLine(canvas);
drawBackGround(canvas);
drawString(canvas);
super.onDraw(canvas);
}
private void drawLine(Canvas canvas) {
for (int row = 0; row <= 9; row++) {
Paint p = new Paint();
// 绘制横线
if (row % 3 == 0) {
// 每三行绘制一条粗线
p.setColor(Color.DKGRAY);
} else {
p.setColor(Color.LTGRAY);
}
float y = row * this.height;
canvas.drawLine(0, y, this.width * 9, y, p);
}
for (int col = 0; col <= 9; col++) {
Paint p = new Paint();
// 绘制竖线
if (col % 3 == 0) {
// 每三行绘制一条粗线
p.setColor(Color.DKGRAY);
} else {
p.setColor(Color.LTGRAY);
}
float x = col * this.width;
canvas.drawLine(x, 0, x, this.height * 9, p);
}
}
private void drawBackGround(Canvas canvas ) {
for (int row = 0; row < 9; row++) {
for (int col = 0; col < 9; col++) {
// 当前选中的单元格,改变背景色
if (orginalArray[row][col] == -1) {
float x = col * width + 1;
float y = row * height + 1;
Paint p=new Paint();
p.setColor( Color.rgb(255, 250, 205));
//p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SCREEN));
canvas.drawRect(x , y, x+width - 2, y+height - 2,p);
}
}
}
}
private void drawString(Canvas canvas) {
Paint number_paint=new Paint();
number_paint.setColor(Color.BLACK);
//number_paint.setStyle(Paint.Style.STROKE);
number_paint.setTextSize(height*0.75f);
number_paint.setTextAlign(Paint.Align.CENTER);
Paint.FontMetrics fm=number_paint.getFontMetrics();
float x=width/2;
float y=height/2-(fm.ascent+fm.descent)/2;
for (int row = 0; row < 9; row++) {
for (int col = 0; col < 9; col++) {
if (orginalArray[row][col] > 0) {
number_paint.setColor(Color.BLACK); // 如果原始生成的字体为黑色
} else {
number_paint.setColor(Color.GREEN); // 如果手动填充的字体为橘色
}
String text = currentArray[row][col] > 0 ? "" + currentArray[row][col] : "";
canvas.drawText(text, width*col+x, height*row+y, number_paint);
}
}
}