前面两篇博文介绍了数独终盘生成的两种方法。
Swing数独游戏(一):终盘生成之矩阵转换法 ==> [url]http://mouselearnjava.iteye.com/blog/1941483[/url]
Swing数独游戏(二):终盘生成之随机法 ==> [url]http://mouselearnjava.iteye.com/blog/1941693[/url]
拥有了数独终盘之后,我们需要在这个终盘上挖去一些数字,然后就能产生数独难题。
在这篇博文中将简单介绍一下“挖洞”法生成数独难题的方式,并采用随机挖洞的方式,对同一份数独终盘产生不同难度的数独难题。
“挖洞”的方式可以有多种实现,一般有四种方式:
参考自:[url]http://zhangroup.aporc.org/images/files/Paper_3485.pdf[/url]
[img]http://dl2.iteye.com/upload/attachment/0089/2848/268d35b1-28e1-3acc-8029-691ceb0ec0aa.jpg[/img]
本文采用随机法来实现。
生成的步骤如下:
[b]1. 首先采用前面两篇文章提供的产生数独终盘的方式,产生1000个.txt文件,作为每一关关卡的数独终盘。[/b]
Swing数独游戏(一):终盘生成之矩阵转换法 ==> [url]http://mouselearnjava.iteye.com/blog/1941483[/url]
Swing数独游戏(二):终盘生成之随机法 ==> [url]http://mouselearnjava.iteye.com/blog/1941693[/url]
[b]2. 每个数独终盘由9个 3 * 3 的块组成,根据设定的关卡难度随机产生一个适合关卡难度的随机数,比如5, 然后在某个 3*3的块中随机剔除5个数。[/b]
剔除数据的位置记为1,代表这个位置没有数字,用户可以输入数字。保留数字的位置记为0, 用于在页面上显示出数字,但用户不能对其进行修改操作。
比如:
我们可以定义游戏难度为四个等级:简单,中等,困难和非常困难。
定义每个困难等级随机数剔除数据的数目:
[b]简单 --> [4,5]
中等 --> [5,6]
困难 --> [5,8]
非常困难 --> [6,9][/b]
[b]3. 根据关卡的数独终盘及其根据困难等级随机挖空某些数据产生的文件一起,共同确定数独难题[/b]
比如,第一关对应的数独终盘如下图所示,终盘内容存储在文件1.txt中。
[img]http://dl2.iteye.com/upload/attachment/0089/2917/b8588a56-3f72-3e43-a5f7-c36d6b96ce65.jpg[/img]
结合为简单难度产生的“挖洞”后的文件,生成对应的数独难题:
[list]
[*]简单难度==>
[img]http://dl2.iteye.com/upload/attachment/0089/2919/d9a92345-819f-3abb-9f5a-796d77d75921.jpg[/img]
同理可以获得中等难度,困难难度等对应的数独难题。
[*]中等难度==>
[img]http://dl2.iteye.com/upload/attachment/0089/2921/21d63525-62bb-3816-aa04-1612e3239333.jpg[/img]
[*]困难难度==>
[img]http://dl2.iteye.com/upload/attachment/0089/2923/798cc6e3-b76b-3e1d-a51d-7074a2bac4ef.jpg[/img]
[/list]
这样一来,不同困难难度的数独难题就产生了,一个关卡,根据剔除数据个数的不同,将为每个困难等级产生一个挖洞文件,也就是有四个数独难题。如果随机产生1000个数独终盘,那么玩家可以玩4000个数独题目,每个困难等级1000个关卡可供选择。
[b]总结:[/b]
本篇文章使用简单的随机剔除数据的方法去产生数独难题,这个方法比较方便简单。但是并不能保证解的唯一性,也就是一道数独难题可能拥有超过一个解法。如果用户填入的数据都满足数独条件,那么他的解就是可用的,有效的,可以通过该关卡。
另外随机剔除数据可能没有剔除某些数据的关联性,可能使得尽管剔除的数据不少,但是玩起来并不难的情况。
下面是随机剔除数据的一个工具类:
Swing数独游戏(一):终盘生成之矩阵转换法 ==> [url]http://mouselearnjava.iteye.com/blog/1941483[/url]
Swing数独游戏(二):终盘生成之随机法 ==> [url]http://mouselearnjava.iteye.com/blog/1941693[/url]
拥有了数独终盘之后,我们需要在这个终盘上挖去一些数字,然后就能产生数独难题。
在这篇博文中将简单介绍一下“挖洞”法生成数独难题的方式,并采用随机挖洞的方式,对同一份数独终盘产生不同难度的数独难题。
“挖洞”的方式可以有多种实现,一般有四种方式:
参考自:[url]http://zhangroup.aporc.org/images/files/Paper_3485.pdf[/url]
[img]http://dl2.iteye.com/upload/attachment/0089/2848/268d35b1-28e1-3acc-8029-691ceb0ec0aa.jpg[/img]
本文采用随机法来实现。
生成的步骤如下:
[b]1. 首先采用前面两篇文章提供的产生数独终盘的方式,产生1000个.txt文件,作为每一关关卡的数独终盘。[/b]
Swing数独游戏(一):终盘生成之矩阵转换法 ==> [url]http://mouselearnjava.iteye.com/blog/1941483[/url]
Swing数独游戏(二):终盘生成之随机法 ==> [url]http://mouselearnjava.iteye.com/blog/1941693[/url]
[b]2. 每个数独终盘由9个 3 * 3 的块组成,根据设定的关卡难度随机产生一个适合关卡难度的随机数,比如5, 然后在某个 3*3的块中随机剔除5个数。[/b]
剔除数据的位置记为1,代表这个位置没有数字,用户可以输入数字。保留数字的位置记为0, 用于在页面上显示出数字,但用户不能对其进行修改操作。
比如:
我们可以定义游戏难度为四个等级:简单,中等,困难和非常困难。
定义每个困难等级随机数剔除数据的数目:
[b]简单 --> [4,5]
中等 --> [5,6]
困难 --> [5,8]
非常困难 --> [6,9][/b]
/**
* 根据不同的游戏难度,获取随机数
*/
public int getRandomNumberByLevel(DifficultyLevel level) {
int randomValue = 5;
switch (level) {
case EASY:
/**
* 产生随机数[4,5]
*/
randomValue = random.nextInt(2) + 4;
break;
case MEDIUM:
/**
* 产生随机数[5,6]
*/
randomValue = random.nextInt(2) + 5;
break;
case DIFFICULT:
/**
* 产生随机数[5,8]
*/
randomValue = random.nextInt(4) + 5;
break;
case EVIL:
/**
* 产生随机数[6,9]
*/
randomValue = random.nextInt(4) + 6;
break;
default:
break;
}
return randomValue;
}
[b]3. 根据关卡的数独终盘及其根据困难等级随机挖空某些数据产生的文件一起,共同确定数独难题[/b]
比如,第一关对应的数独终盘如下图所示,终盘内容存储在文件1.txt中。
[img]http://dl2.iteye.com/upload/attachment/0089/2917/b8588a56-3f72-3e43-a5f7-c36d6b96ce65.jpg[/img]
结合为简单难度产生的“挖洞”后的文件,生成对应的数独难题:
[list]
[*]简单难度==>
[img]http://dl2.iteye.com/upload/attachment/0089/2919/d9a92345-819f-3abb-9f5a-796d77d75921.jpg[/img]
同理可以获得中等难度,困难难度等对应的数独难题。
[*]中等难度==>
[img]http://dl2.iteye.com/upload/attachment/0089/2921/21d63525-62bb-3816-aa04-1612e3239333.jpg[/img]
[*]困难难度==>
[img]http://dl2.iteye.com/upload/attachment/0089/2923/798cc6e3-b76b-3e1d-a51d-7074a2bac4ef.jpg[/img]
[/list]
这样一来,不同困难难度的数独难题就产生了,一个关卡,根据剔除数据个数的不同,将为每个困难等级产生一个挖洞文件,也就是有四个数独难题。如果随机产生1000个数独终盘,那么玩家可以玩4000个数独题目,每个困难等级1000个关卡可供选择。
[b]总结:[/b]
本篇文章使用简单的随机剔除数据的方法去产生数独难题,这个方法比较方便简单。但是并不能保证解的唯一性,也就是一道数独难题可能拥有超过一个解法。如果用户填入的数据都满足数独条件,那么他的解就是可用的,有效的,可以通过该关卡。
另外随机剔除数据可能没有剔除某些数据的关联性,可能使得尽管剔除的数据不少,但是玩起来并不难的情况。
下面是随机剔除数据的一个工具类:
package my.sudoku.utils;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Random;
import java.util.logging.Logger;
import my.sudoku.constant.SudokuContants;
import my.sudoku.enums.DifficultyLevel;
public class HoleDigUtils {
private static final Logger logger = Logger
.getLogger("my.sudoku.utils.HoleDigUtils");
private Random random = new Random();
public void digHolesByGameDifficulty(int numOfFiles, DifficultyLevel level) {
for (int num = 1; num <= numOfFiles; num++) {
int[][] array = new int[9][9];
int randomInt = 0;
for (int i = 0; i < 9; i++) {
randomInt = getRandomNumberByLevel(level);
int[] randomPositions = populateRandomArray(randomInt);
for (int j = 0; j < randomPositions.length; j++) {
int col = (i % 3) * 3 + (randomPositions[j] - 1) % 3;
int row = (i / 3) * 3 + ((randomPositions[j] - 1) / 3);
array[row][col] = 1;
}
/**
* 将array写入文件
*/
BufferedWriter bw = null;
try {
bw = new BufferedWriter(new FileWriter(new File(
SudokuContants.SUDOKU_FOLDER_NAME, buildFileName(
level, num))));
} catch (IOException e) {
logger.severe(e.getMessage());
}
StringBuilder sb = new StringBuilder();
for (int k = 0; k < 9; k++) {
sb.setLength(0);
for (int j = 0; j < 9; j++) {
sb.append(array[k][j]);
sb.append(",");
}
try {
bw.write(sb.substring(0, sb.length() - 1).toString());
bw.newLine();
} catch (IOException e) {
logger.severe(e.getMessage());
}
}
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
logger.severe(e.getMessage());
} finally {
bw = null;
}
}
}
}
}
private String buildFileName(DifficultyLevel level, int fileNumberl) {
StringBuilder sb = new StringBuilder();
sb.append(fileNumberl);
switch (level) {
case EASY:
sb.append("_easy.txt");
break;
case MEDIUM:
sb.append("_medium.txt");
break;
case DIFFICULT:
sb.append("_difficult.txt");
break;
case EVIL:
sb.append("_evil.txt");
break;
default:
break;
}
return sb.toString();
}
/**
* 根据不同的游戏难度,获取随机数
*/
public int getRandomNumberByLevel(DifficultyLevel level) {
int randomValue = 5;
switch (level) {
case EASY:
/**
* 产生随机数[4,5]
*/
randomValue = random.nextInt(2) + 4;
break;
case MEDIUM:
/**
* 产生随机数[5,7]
*/
randomValue = random.nextInt(3) + 5;
break;
case DIFFICULT:
/**
* 产生随机数[5,8]
*/
randomValue = random.nextInt(4) + 5;
break;
case EVIL:
/**
* 产生随机数[6,9]
*/
randomValue = random.nextInt(4) + 6;
break;
default:
break;
}
return randomValue;
}
private int[] populateRandomArray(int numOfRandoms) {
int[] array = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int randomInt = 0;
for (int i = 0; i < 20; i++) {
randomInt = random.nextInt(8) + 1;
int temp = array[0];
array[0] = array[randomInt];
array[randomInt] = temp;
}
int[] result = new int[numOfRandoms];
System.arraycopy(array, 0, result, 0, numOfRandoms);
return result;
}
public static void main(String[] args) throws IOException {
HoleDigUtils digger = new HoleDigUtils();
/**
* 采用"挖洞"法,产生不同难度的数独难题文件。
* 文件中 0 表示 有数据且不可编辑,1 表示不显示数据,且可编辑
*/
for (DifficultyLevel level : DifficultyLevel.values()) {
digger.digHolesByGameDifficulty(
SudokuContants.NUMBER_OF_SUDOKU_ARRAYS, level);
}
}
}