必威体育Betway必威体育官网
当前位置:首页 > IT技术

1.数独游戏(生成题目解唯一)

时间:2019-08-10 09:43:17来源:IT技术作者:seo实验室小编阅读:82次「手机版」
 

数独游戏题目

——————————————————————————

2019年6月15日

mark:给朋友玩了一下,评价是比较简单,难度不大,因为

挖空数量不够。暑假有时间就升级一下算法,多挖几个空

——————————————————————————

前几天在玩数独游戏的时候,产生了一个大胆的想法:

数独APP上的题目都是现成的,干脆自己写一个可以随机生成数独的程序算了

一、需求提出:

1.随机生成数独题目,要求该题目有解(有一个解最好);

2.当填数违反数独操作(填到题目原本的数字去了,填数违规等)时,禁止操作,弹出提示;

3.当81格都填满时,判断对错;

二、需求分析

总:

需求2,3相对简单,尤其是需求3,都禁止违规操作了,填满81格不就赢了吗?也许是当初没有给自己再次组织语言的机会,提出了这么傻缺的需求,核心问题就是需求1。

需求1

首先得到一个9*9,符合数独规则的矩阵,然后随机挖空不就行了吗?所以需求1的问题转化为如何得到一个随机9*9数独矩阵。

第一个想出来的方法是用搜索算法,在每一个格子随机生成1-9的数字,然后判断符不符合规则,再下一个。

这个算法的缺点是效率感人,81个格子,每个格子的数字都要判断,每个格子的数字还都是随机生成的,效率能不感人吗?

优点是还没付诸行动就被否定了,给程序编写省下了不少时间

第二个想法就相对可靠了,先在随机在若干个格子上生成随机数(这些随机数也要符合数独规则),这样就变成了一道题目,然后让电脑用搜索算法求解,只要能解出一种,就停止解题,并且选取这种解答作为随机9*9数独矩阵以及本题的答案。解不出也没有关系重新生成随机数再重新解过就得了。

这个方法可行度很高,优点很多,其中最重要的优点是我在刷蓝桥杯时曾经写过一个解数独的程序

经过调试,我把随机数个数设置在了15,既不会因为太少而使得随机数在矩阵上分布不均匀,又保证不会因为数字太多,答案太少而影响生成效率。

得到这个矩阵(终盘)后,经过简单的随机挖空,一道数独题目就随机生成了。

但是这样的话并不能保证数独有唯一解啊,怎么办呢?

当时我没有想到,过了几个月后发现可以这样做:

挖完空后求解出所有情况,并与终盘对比(不用与第一个求解的对比,因为都是用DFS求解答案的,所以其实第一个答案就是终盘!),哪里不一样就不挖那里不就行了吗?

当然很明显有个缺陷:比如虽然与终盘有N处地方不一样,但是不一定要把N处全部填完啊,只填其中一部分就能保证与终盘一样了啊!而运用上面的做法显然不能挖更多的空。

所以下一步打算(打算而已!)这样改进:每次填1上一个不同的地方,然后再次求解,如果解还是不唯一就再填一个,反正用DFS求解快得一批!

经本人验证,确实达到了唯一解的效果,挖空能达到40~50个,我觉得海星。有时候有点慢,少数情况下甚至会卡死,排除了算法的问题,我也不知道怎么回事。。。

下面演示一个,后面附上解数独器,你们也可以试一试:

题目:

求解,只求出了一种情况:

做个对比:

说了这么多,该贴出代码了。除了对算法的改进外,我用java重写了游戏,从此支持键鼠操作,摆脱小黑框啦!

--背景图:Grid.jpg 362*362。在src下创建img文件夹,然后把图片丢进去就行了

--代码:为了方便使用,我把所有类放在了一个java文件下。src下创建Main包,然后放进去就行了

package Main;

import java.awt.color;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.actionlistener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Random;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextArea;


/*生成随机数独
 * 流程:
 * 1.生成9*9空白数独数组
 * 2.随机往数独数组填数
 * 3.DFS生成数独标准解(即数独数组81个格子都填满数字)
 * 4.先挖n个空,对挖完空的数独进行求解(全部)
 * 5.将所有解与标准解进行对比,将不一样的地方记录下。这些地方不允许被被挖空
 */
class SudokuMaker {
	private int[][] Arr;//临时数组
	private int [][]Sudoku;
	private int [][]Answer;//答案
	private int [][]Game;
	
	public SudokuMaker(){
		this.Arr=new int[9][9];
		this.Sudoku=new int[9][9];
		this.Answer=new int[9][9];
		rand();
		DFS(Arr,0,false);
		diger();
		DevTools.showGame(Game);
		DevTools.showAnswer(Answer);
	}
	
	private void rand(){
		int t=0;
		int x,y,num;
		//先往数组里面随机丢t个数
		while(t<15){//t不宜多,否则运行起来耗费时间;t也不宜少,否则生成的游戏看起来一点也不随机
			x=new Random().nextint(9);
			y=new Random().nextInt(9);
			num=new Random().nextInt(9)+1;
			if(Arr[y][x]==0){
				if(isTrue(Arr,x,y,num)==true){
					Arr[y][x]=num;++t;
				}
			}
		}
	}
	
	//判断该数字填写的地方是否符合数独规则
	public static boolean isTrue(int arr[][],int x,int y,int num){//数字横坐标;数字纵坐标;数字数值
		//判断中单元格(3*3)
		for(int i=(y/3)*3;i<(y/3+1)*3;++i){
			for(int j=(x/3)*3;j<(x/3+1)*3;++j){
				if(arr[i][j]==num ){return false;}
			}
		}
		//判断横竖
		for(int i=0;i<9;++i){
			if((arr[i][x]==num || arr[y][i]==num)){return false;}	
		}
		return true;
	}
	
	//深度优先搜索寻找
	//绝对有很多种解法,但是我们只需要第一个解出来的
	private boolean flag=false;//判断是否不再求解
	int total=0;
	private void DFS(int arr[][],int n,boolean all){//arr是数独数组,n是探索深度(一共81个格子,深度为81,n为0~80),是否要求全部解
		//n/9为格子的纵坐标,n%9为格子的横坐标
		if(n<81){
			//如果已经求出了一种解,终止递归就行了,不用继续求下去了
			if(flag==true && all==false){return;}
			
			if(arr[n/9][n%9]==0){
				for(int i=1;i<10;++i){
					if(isTrue(arr,n%9,n/9,i)==true){
						arr[n/9][n%9]=i;
						DFS(arr,n+1,all);
						arr[n/9][n%9]=0;
					}
				}
			}else{
				DFS(arr,n+1,all);
			}
			
		}else{
			if(all==false){
				flag=true;
				for(int i=0;i<9;++i){
					for(int j=0;j<9;++j){
						Sudoku[i][j]=arr[i][j];
						Answer[i][j]=arr[i][j];
					}
				}
			}else{
				for(int i=0;i<9;++i){
					for(int j=0;j<9;++j){
						if(arr[i][j]!=Answer[i][j]){
							Game[i][j]=Answer[i][j];
							i=10;j=10;
							break;
						}
					}
				}
			}
		}
	}
	
	//给数独挖空
	//保证仅有一解
	private void diger(){
		int t=55;
		Game=new int[9][9];
		while(t>0){
			int x=new Random().nextInt(9);
			int y=new Random().nextInt(9);
			if(Sudoku[y][x]!=0){
				Sudoku[y][x]=0;--t;
			}
		}
		
		for(int i=0;i<9;++i){
			for(int j=0;j<9;++j){
				Game[i][j]=Sudoku[i][j];
			}
		}
		
		DFS(Sudoku,0,true);
	}
	
	//获取最终数独
	public int[][] getArr(){
		return this.Game;
	}
	
	//获取数独答案
	public int[][] getAnswer(){
		return this.Answer;
	}
}


//游戏界面
class UI{
	private JFrame gameUI;
	private JLabel gameGrid;//数独81宫格
	private SudokuMaker game;//数独
	private JLabel[][] smallGrid;//数独小格子
	private UserEvents [][] mouseListen;//监听器
	

	
	public UI(){
		gameUI=new JFrame();
		
		gameUI.setVisible(true);
		gameUI.setlayout(null);
		gameUI.setSize(600,430);
		gameUI.setResizable(false);//不允许窗口最大化
		gameUI.setLocationRelativeTo(null);
		
		JButton bt1=new JButton("生成新数独");
		gameUI.add(bt1);
		bt1.setBounds(400,10,100,20);
		bt1.addActionListener(new OtherGameEvent());
		
		JButton bt2=new JButton("显示答案");
		gameUI.add(bt2);
		bt2.setBounds(400,110,100,20);
		bt2.addActionListener(new ShowAnswer());
		
		
		gameGrid=new JLabel();
		gameGrid.setBounds(10,10,365,365);
		gameUI.add(gameGrid);
		java.net.URL imgURL = Main.class.getResource("/img/Grid.jpg");
		gameGrid.setIcon(new ImageIcon(imgURL));
		gameGrid.setOpaque(true);
		
		Font font=new Font("宋体",Font.BOLD,25);
		
		smallGrid=new JLabel[9][9];
		mouseListen=new UserEvents[9][9];
		for(int i=0;i<9;++i){
			for(int j=0;j<9;++j){
				smallGrid[i][j]=new JLabel("",JLabel.CENTER);
				gameGrid.add(smallGrid[i][j]);
				mouseListen[i][j]=new UserEvents(smallGrid[i][j],i,j,false);
				smallGrid[i][j].setFont(font);
				smallGrid[i][j].setBounds(j*40+1,i*40+1,40,40);
				smallGrid[i][j].addMouseListener(mouseListen[i][j]);
			}
		}
		newGame();
	}
	
	//新游戏
	private void newGame(){

		game=new SudokuMaker();
		int [][]gameArr=game.getArr();
		for(int i=0;i<9;++i){
			for(int j=0;j<9;++j){
				if(gameArr[i][j]!=0){	
					smallGrid[i][j].setText(gameArr[i][j]+"");
					mouseListen[i][j].setUse(false);
					smallGrid[i][j].setForeground(Color.black);
				}else{
					smallGrid[i][j].setText(null);
					mouseListen[i][j].setUse(true);
				}
			}
		}
	}
	
	private class ShowAnswer implements ActionListener{

		@Override
		public void actionPerformed(ActionEvent arg0) {
			// TODO Auto-generated method stub
			for(int i=0;i<9;++i){
				for(int j=0;j<9;++j){
					if(mouseListen[i][j].getUse()==true){
						smallGrid[i][j].setText(game.getAnswer()[i][j]+"");
						smallGrid[i][j].setForeground(Color.BLUE);
						mouseListen[i][j].setUse(false);
					}
				}
			}
		}
		
	}
	
	private class OtherGameEvent implements ActionListener{
		@Override
		public void actionPerformed(ActionEvent arg0) {
			// TODO Auto-generated method stub
			newGame();
		}
	}
	
	private class UserEvents implements MouseListener{
		
		private JTextArea jta;
		private JLabel focus;//聚焦
		private int gridX;
		private int gridY;
		private boolean isUse;//是否开启事件


		public UserEvents(JLabel f,int y,int x,boolean u){
			focus=f;
			gridX=x;
			gridY=y;
			isUse=u;
			
			jta=new JTextArea();
			jta.setBounds(5,5,30,30);
			jta.setVisible(false);
			jta.setOpaque(false);
			jta.setFont(new Font("宋体",Font.BOLD,25));
			focus.add(jta);
		}
		
		@Override
		public void mouseClicked(MouseEvent arg0) {
			// TODO Auto-generated method stub
			if(isUse==true){

				focus.setBorder(BorderFactory.createLineBorder(Color.red,5));
				jta.setVisible(true);
				focus.setText(null);
			}
		}

		@Override
		public void mouseEntered(MouseEvent arg0) {
			// TODO Auto-generated method stub

		}
		
		@Override
		public void mouseExited(MouseEvent arg0) {
			// TODO Auto-generated method stub
			int X=arg0.getX(),Y=arg0.getY();
			if(isUse==true){
				if(X<=0 || X>=40 || Y<=0 || Y>=40){
					focus.setBorder(BorderFactory.createLineBorder(Color.red,0));
					try{
						int t=new integer(jta.getText());
						if(t>0 && t<10){
							game.getArr()[gridY][gridX]=0;
							if(SudokuMaker.isTrue(game.getArr(), gridX, gridY, t)==true){
								focus.setForeground(Color.green);
							}else{
								focus.setForeground(Color.red);
							}
							game.getArr()[gridY][gridX]=t;
							}
							focus.setText(jta.getText());
					}catch(Exception e){
						jta.setText(null);
					}
					jta.setVisible(false);
				}
			}
		}

		@Override
		public void mousePressed(MouseEvent arg0) {
			// TODO Auto-generated method stub
			if(this.isUse==true){
				focus.setBorder(BorderFactory.createLineBorder(Color.red,5));
			}
		}

		@Override
		public void mouseReleased(MouseEvent arg0) {
			// TODO Auto-generated method stub
		}
		
		public void setUse(boolean u){
			this.isUse=u;
			if(u==true){
				jta.setText(null);
			}else{
				focus.setBorder(BorderFactory.createLineBorder(Color.red,0));
			}
		}
		
		
		public boolean getUse(){
			return isUse;
		}

	}
	
}

public class Main {

	
	//测试方法
	public static void main(String arg[]){
		new UI();
	}
}

//开发工具包
class DevTools{
	
	public static void showAnswer(int arr[][]){
		System.out.println("\n答案:");
		for(int i[]:arr){
			for(int j:i){
				System.out.print(j);
			}
			System.out.println();
		}
		System.out.println();
	}
	
	public static void showGame(int arr[][]){
		System.out.println("\n题目:");
		int total=0;
		for(int i[]:arr){
			for(int j:i){
				System.out.print(j);
				if(j==0){++total;}
			}
			System.out.println();
		}
		System.out.println("挖空数:"+total);
	}
}

--解数独器:可以用来验证一下是不是唯一解:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
#include<windows.h>
int a[9][9],total;

int bfs(int x,int y,int num){
	int m,n,time=0;
	int dir[9][2]={{1,1},{1,0},{1,-1},{0,1},{0,-1},{-1,1},{-1,0},{-1,-1},{0,0}};
	for(int i=0;i<9;++i){
		m=x+dir[i][0],n=y+dir[i][1];
		if(num==a[n][m]){
			++time;
		}
		if(time>1){
			return 0;
		}
	}
	return 1;
}

int judge(int x,int y,int n){
	int t=0;
	for(int i=0;i<9;++i){
		if(a[y][i]==n){
			++t;
			if(t>1){
				return 1;
			}
		}
	}
	t=0;
	for(int i1=0;i1<9;++i1){
		if(a[i1][x]==n){
			++t;
			if(t>1){
				return 1;
			}
		}
	}
	int nu,m;
	if(bfs(1+(x/3)*3,1+(y/3)*3,n)==0){
		return 1;
	}
	return 0;
}


void sel(int x,int y){
	if(y<9){
		if(x<9){
			if(a[y][x]==0){
				for(int i=1;i<=9;++i){
					a[y][x]=i;
					if(judge(x,y,i)==0){
						sel(x+1,y);
					}
					a[y][x]=0;
				}
			}else{
				sel(x+1,y);
			}
		}else{
			sel(0,y+1);
		}
	}else{
		++total;
		printf("\n\n");
		for(int i=0;i<9;++i){
			for(int j=0;j<9;++j){
				printf("%d",a[i][j]);
			}
			printf("\n");
		}
		
	}
}

int main(){
	printf("输入格式:\n");
	printf("005300000\n800000020\n070010500\n400005300\n010070006\n003200080\n060500009\n004000030\n000009700\n");
	printf("\n\n请输入数独题:\n");
	for(int i=0;i<9;++i){
		int line;
		scanf("%d",&line);
		for(int j=0;j<9;++j){
			a[i][8-j]=line%10;
			line/=10;
		}
	} 
	sel(0,0);
	Sleep(100000);
	return 0;
}

相关阅读

SSN 生成器 美国地址社保号生成器 V2.24

真实的SSN资料获取器,亲切可用来注册 square刷卡器官方账号,美国POST机,国外网站 Lead,美国虚拟银行等! 从国外某秘密网站VIP区下的

使用Java生成的ZIP压缩包解压时出现不可预料的压缩文

使用Java生成的ZIP压缩包解压时出现不可预料的压缩文件末端的解决方案 问题描述: 如下图所示,在解压Java程序生成的ZIP压缩包时出

密码字典生成工具Cupp和Cewl学习笔记

Cupp是一款用Python语言写成的可交互性的字典生成脚本。尤其适合社会工程学,当你收集到目标的具体信息后,你就可以通过这个工具来智

logback 每天生成和大小生成 TimeBasedRollingPolicy

项目使用了logback,日志打印需要按照每天和大小生成日志,于是使用了TimeBasedRollingPolicy SizeBasedTriggeringPolicy[html] vie

matlab生成随机数的rand、randi和randn三种形式

  matlab中关于随机数的产生有3种库函数,下面我们来看看它们的形式:   1、rand(…)   它是生成0~1之间(开环,不包含0和1两个数

分享到:

栏目导航

推荐阅读

热门阅读