Swing数独游戏(三):数独难题生成
前面两篇博文介绍了数独终盘生成的两种方法。
Swing数独游戏(一):终盘生成之矩阵转换法 ==> http://mouselearnjava.iteye.com/blog/1941483
Swing数独游戏(二):终盘生成之随机法 ==> http://mouselearnjava.iteye.com/blog/1941693
拥有了数独终盘之后,我们需要在这个终盘上挖去一些数字,然后就能产生数独难题。
在这篇博文中将简单介绍一下“挖洞”法生成数独难题的方式,并采用随机挖洞的方式,对同一份数独终盘产生不同难度的数独难题。
“挖洞”的方式可以有多种实现,一般有四种方式:
参考自:http://zhangroup.aporc.org/images/files/Paper_3485.pdf
本文采用随机法来实现。
生成的步骤如下:
1. 首先采用前面两篇文章提供的产生数独终盘的方式,产生1000个.txt文件,作为每一关关卡的数独终盘。
Swing数独游戏(一):终盘生成之矩阵转换法 ==> http://mouselearnjava.iteye.com/blog/1941483
Swing数独游戏(二):终盘生成之随机法 ==> http://mouselearnjava.iteye.com/blog/1941693
2. 每个数独终盘由9个 3 * 3 的块组成,根据设定的关卡难度随机产生一个适合关卡难度的随机数,比如5, 然后在某个 3*3的块中随机剔除5个数。
剔除数据的位置记为1,代表这个位置没有数字,用户可以输入数字。保留数字的位置记为0, 用于在页面上显示出数字,但用户不能对其进行修改操作。
比如:
我们可以定义游戏难度为四个等级:简单,中等,困难和非常困难。
定义每个困难等级随机数剔除数据的数目:
简单 --> [4,5]
中等 --> [5,6]
困难 --> [5,8]
非常困难 --> [6,9]
结合为简单难度产生的“挖洞”后的文件,生成对应的数独难题:
简单难度==>

同理可以获得中等难度,困难难度等对应的数独难题。
中等难度==>

困难难度==>

这样一来,不同困难难度的数独难题就产生了,一个关卡,根据剔除数据个数的不同,将为每个困难等级产生一个挖洞文件,也就是有四个数独难题。如果随机产生1000个数独终盘,那么玩家可以玩4000个数独题目,每个困难等级1000个关卡可供选择。
总结:
本篇文章使用简单的随机剔除数据的方法去产生数独难题,这个方法比较方便简单。但是并不能保证解的唯一性,也就是一道数独难题可能拥有超过一个解法。如果用户填入的数据都满足数独条件,那么他的解就是可用的,有效的,可以通过该关卡。
另外随机剔除数据可能没有剔除某些数据的关联性,可能使得尽管剔除的数据不少,但是玩起来并不难的情况。
下面是随机剔除数据的一个工具类:
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);}}}