看得见的算法——扫雷

看得见的算法——扫雷

windwos一直以来都自带一款扫雷游戏那么我们来实现这个游戏(主要在于游戏算法)

那么我们该如何实现该算法:

1.首先我们需要一个布尔数组来表示这个地方是雷还是其他的什么记为bollean mine[][]

2.我们还需要一个二维数组来记录以点击坐标为中心九宫格中的雷的数量记为int[][] number

3.最后我们还需要一个布尔数组来记录此位置是被点开和一个布尔数组来标记是否插旗分别记为boolean[][] open,boolean[][] flag

4.根据以上数组我们先对它们进行初始化行为M,列为N

5.现在我们得到的mine全为false那么我们需要让一部分为true(即雷),并且顺序是被打乱的,即公平的乱序算法布雷

6.这里我们采用一个叫做Fisher-Yates的乱序算法它的随机可能性在0.5+(-)0.1上下波动比我们的一般的随机化要强一些,这个算法的思路很简单就是把当前的元素与剩余的元素中的随机一个做交换(包括它自己),每次随机完后可以随机的的元素数量就减一,也就是说随机过后就不参与随机这个乱序算法每个元素的可能性将会是一个n的阶乘,n为元素的数量

7.现在开始随机化雷我们先传入一个mineNumber这是生成雷的个数,mineNumber/M(mine的行数)为第几行,mineNumber%M(mine的行数)为第几列,这个也叫做一维数组向二维数组的映射,我们将mine前mineNumber置为false,然后通过乱序法打乱顺序使其分布具有随机性

8.我们乱序完之后还要对每个单元周围的九宫格雷的数量进行统计,我们使用8向统计如果要统计的单元是个雷我们给其置-1,否则我们置0然后对其八个方向进行判断如果下标合法并且是雷的话则number的坐标对应位置++

MineSweeperData(数据类)

package com.lipengge.minesweeper.data;

public class MineSweeperData {
	private int M;//行数
	private int N;//列数
	public boolean mine[][];//雷区
	public int number[][];//用来标记某个像素每个九宫格内炸弹的数量
	public boolean[][] open;//用来标记是否点开
	public boolean[][] flag;//用来标记是否插旗
	public MineSweeperData(int M,int N,int mineNumber){
		if(M<0||N<0){
			throw new IllegalArgumentException("传入数组大小非法");
		}
		this.M=M;
		this.N=N;
		mine=new boolean[M][N];
		number=new int[M][N];
		open=new boolean[M][N];
		flag=new boolean[M][N];
		//初始化雷区
		generateMine(M, N,mineNumber);
		//雷区计数
		countMine(M,N);
	}
	//计数
	private void countMine(int M, int N) {
		for(int i=0;i<M;i++){
			for(int j=0;j<N;j++){
				if(isMine(i, j)){
					number[i][j]=-1;
				}else{
				number[i][j]=0;
			   for(int k=i-1;k<=i+1;k++){
				   for(int q=j-1;q<=j+1;q++){
					   if(isArea(k, q)&&isMine(k,q)){
						   number[i][j]++;
					   }
				   }
			   }
			}
			}
		}
	}
	private void generateMine(int M, int N, int mineNumber) {
		//初始化雷区
		for(int i=0;i<mineNumber;i++){
			int x=i/M;
			int y=i%M;
			mine[x][y]=true;
		}
		//打乱雷区
		for(int i=M*N-1;i>=0;i--){
			int x=i/M;
			int y=i%M;
			int x1=(int)(Math.random()*i+1)/M;
			int y1=(int)(Math.random()*i+1)%M;
			swap(x,y,x1,y1);
		}
	}
	//交换
	private void swap(int x, int y, int x1, int y1) {
		boolean temp=mine[x][y];
		mine[x][y]=mine[x1][y1];
		mine[x1][y1]=temp;
	}
	//获取雷区元素
	public boolean isMine(int x,int y){
		if(!isArea(x,y)){
			throw new IllegalArgumentException("数组下标越界");
		}
		return mine[x][y];
	}
	public int getM() {
		return M;
	}
	public int getN() {
		return N;
	}
	//判断下标是否合法
	public boolean isArea(int x,int y){
		return x>=0&&y>=0&&x<M&&y<N;
	}
  
}

视图类

这里要说说我们的绘制当单元是open时:当是雷是我们绘制类的图片,当不是雷时我们绘制一个1-8的雷的个数或者什么都没有当没有打开时如果被标记则绘制棋子否则绘制背景

package com.lipengge.minesweeper.view;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

import com.lipengge.minesweeper.data.MineSweeperData;
import com.lipengge.minesweeper.util.AlgoVisHelper;
import com.lipengge.minesweeper.util.ImageUrlUtil;



public class AlgoFrame extends JFrame{
	/**
	 * 
	 */
	private static final long serialVersionUID = -3035088527551930125L;
	private int canvasWidth;//画布的宽
	private int canvasHeight;//画布的长
	private MineSweeperData data;//雷区的数据类型
   public int getCanvasWidth() {
		return canvasWidth;
	}
	public int getCanvasHeight() {
		return canvasHeight;
	}
	public void render(MineSweeperData data){
		this.data=data;
		repaint();
	}
   public AlgoFrame(String title,int  canvasWidth, int canvasHeight){
	  super(title);
	  this.canvasWidth=canvasWidth;
	  this.canvasHeight=canvasHeight;
	  setDefaultCloseOperation(EXIT_ON_CLOSE);
	  setResizable(false);//关闭可拖动
	  setVisible(true);
	  AlgoCanvas algoCanvas = new AlgoCanvas();
	  setContentPane(algoCanvas);
	  pack();
   }
   //设置面板JPanel
   class AlgoCanvas extends JPanel{
	/**
	 * 
	 */
	private static final long serialVersionUID = -6056196800598676936L;
    private  AlgoCanvas(){
    	//开启双缓存
    	super(true);
    }
	@Override
	//在面板上绘制内容
	protected void paintComponent(Graphics g) {
		super.paintComponent(g);
		Graphics2D g2d=(Graphics2D) g;
		//每个单元占的宽
		int w=canvasWidth/data.getN();
		//每个单元占的高
		int h=canvasHeight/data.getM();
		//绘制逻辑
		for(int i=0;i<data.getM();i++){
			for(int j=0;j<data.getN();j++){
				//如果打开
				if(data.open[i][j]){
				if(data.isMine(i, j)){
					AlgoVisHelper.drawImage(g2d,ImageUrlUtil.MINE_IMAGE,j*w,i*h,w,h);
				}else{
					AlgoVisHelper.drawImage(g2d,ImageUrlUtil.getMineNumber(data.number[i][j]),j*w,i*h,w,h);
				}
			}else{
				if(data.flag[i][j]){
					AlgoVisHelper.drawImage(g2d,ImageUrlUtil.FLAG_IMAGE,j*w,i*h,w,h);
				}else{
					AlgoVisHelper.drawImage(g2d,ImageUrlUtil.BLOCK_IMAGE,j*w,i*h,w,h);
				}
			}
			}
		}
	}
	@Override
	public Dimension getPreferredSize() {
		return new Dimension(canvasWidth,canvasHeight);
	}
   }
   //用来弹出各种信息
   public void showInfo(String info){
	   JOptionPane.showMessageDialog(this, info);![在这里插入图片描述](https://img-blog.csdnimg.cn/20190717221719622.png)
   }
}![在这里插入图片描述](https://img-blog.csdnimg.cn/20190717221638680.png)

下面是图片

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

控制层

我们在控制层除了实例化我们的数据层之外我们还干了以下几件事情:

1.我们添加了一个鼠标键的释放事件用来处理点击事件这里我们需要注意的是:

1.我们直接获取的点击坐标是不准确的因为我们的窗体有菜单栏必须向上移动这个距离才能准确获取坐标,然后就是坐标和数组下标的转换由于我们面板的宽和高都是根据数组的行和列分别乘上正方形图片的像素32所以数组的行应该为pos.y/32,列为pos.x/32这里注意行用坐标的y转换,列用坐标的x转换
2.获取到坐标之后我们首先判断是点击的是鼠标左键还是鼠标右键如果是左键则打开当前单元如果不是雷对其进行八向floodfill算法直到遇到边界停止,floodfill算法就是取图一点然后进行泛滥填充直到遇到边界,实质还是图的遍历本程序使用递归的深度优先遍历,广度优先遍历和非递归深度优先遍历也可以实现,是雷调用bang方法游戏失败所有雷全部打开,如果点击右键则对应坐标插上旗
3.我在程序里只添加了游戏失败的逻辑成功的很简单思路是只要把所有的雷全部标上旗子打开所有单元格游戏成功这里提示一下我们只要在每次右键时遍历一次数组即可
4.游戏失败的逻辑是只要点击打开的是雷则全部雷打开游戏失败,重新绘制窗体
5.在递归里面如果是记录雷的数字则返回上一层我们以这个作为边界
package com.lipengge.minesweeper.controller;

import java.awt.EventQueue;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.SwingUtilities;

import com.lipengge.minesweeper.data.MineSweeperData;import com.lipengge.minesweeper.util.AlgoVisHelper;
import com.lipengge.minesweeper.util.ImageUrlUtil;
import com.lipengge.minesweeper.view.AlgoFrame;


public class AlgoVisualizer {
    private AlgoFrame frame;
    private MineSweeperData data;
    private int canvasWidth;
    private int canvasHeight;
    private int  mineNumber;
	public AlgoVisualizer(int M,int N,int mineNumber) {
	  data=new MineSweeperData(M, N, mineNumber);
	  canvasWidth=ImageUrlUtil.BLOCK_WIDTH*data.getN();
	  canvasHeight=ImageUrlUtil.BLOCK_WIDTH*data.getM();
	  this.mineNumber=mineNumber;
	  EventQueue.invokeLater(()->{
		  frame=new AlgoFrame("扫雷", canvasWidth, canvasHeight);
		  frame.addMouseListener(new AlgoMouseListener());
		  new Thread(()->{
			  run();
		  }).start();
	  });
		  
	  }
	private void run() {
		setData(false,-1,-1);
	}
	private void setData(boolean isopen,int x,int y) {
		if(data.isArea(x, y)){
			if(isopen){
				data.open[x][y]=true;
			}else{
				data.flag[x][y]=true;
			}
		}
		frame.render(data);
		AlgoVisHelper.pause(20);
	}
	class AlgoMouseListener extends MouseAdapter{
		 public void mouseReleased(MouseEvent e) {
			e.translatePoint(-(int)(frame.getBounds().width-canvasWidth), -(int)(frame.getBounds().height-canvasHeight));
			Point pos=e.getPoint();
			int x=pos.y/ImageUrlUtil.BLOCK_WIDTH;
			int y=pos.x/ImageUrlUtil.BLOCK_WIDTH;
			if(SwingUtilities.isRightMouseButton(e)){
				setData(false,x,y);
			}else if(SwingUtilities.isLeftMouseButton(e)){
				setData(true, x, y);
				if(!data.mine[x][y]){
				open(x,y);
				}else{
					try{
					bang();
					frame.showInfo("游戏失败");
					}catch(Exception l){
						
					}finally{
					data=new MineSweeperData(data.getM(),data.getN(), mineNumber);
					setData(false,-1,-1);
					}
				}
			}
		    
		 }
		private void open(int x, int y) {
			if(data.number[x][y]>0){
				return;
			}
			data.open[x][y]=true;
			for(int i=x-1;i<=x+1;i++){
				for(int j=y-1;j<=y+1;j++){
					if(data.isArea(i, j)&&!data.open[i][j]&&!data.isMine(i, j)){
						open(i,j);
					}
				}
			}
		}
	}
	public void bang(){
		for(int i=0;i<data.getM();i++){
			for(int j=0;j<data.getN();j++){
				if(data.isMine(i, j)){
					setData(true,i,j);
				}
			}
		}
	}
    public static void main(String[] args){
    	new AlgoVisualizer(30,30, 40);
    }
}

工具类

图片的常量

package com.lipengge.minesweeper.util;

public class ImageUrlUtil {
	public static final int BLOCK_WIDTH=32;
	public static final String BASE_URL="resource";
	public static final String BLOCK_IMAGE=BASE_URL+"/block.png";
	public static final String MINE_IMAGE=BASE_URL+"/mine.png";
	public static final String FLAG_IMAGE=BASE_URL+"/flag.png";
	public static String getMineNumber(int num){
		if(num<0||num>8){
			throw new IllegalArgumentException("请输入正确的数字");
		}
		return BASE_URL+"/"+num+".png";
	}

}

绘制的帮助类

package com.lipengge.minesweeper.util;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;

import javax.swing.ImageIcon;

public class AlgoVisHelper {
	private AlgoVisHelper(){}
	//画圆描边的辅助类
	public static void strokeCircle(Graphics2D g,int x,int y,int r){
		//圆数据的闭包
		Ellipse2D circle = new Ellipse2D.Double(x-r,y-r,2*r,2*r);
		g.draw(circle);
	}
	//画圆填充的辅助类
	public static void fillCircle(Graphics2D g,int x,int y,int r){
		Ellipse2D circle = new Ellipse2D.Double(x-r,y-r,2*r,2*r);
		g.fill(circle);
	}
	//画矩形的辅助类
	public static void strokeReactangle(Graphics2D g,int x,int y,int width,int height){
		Rectangle2D reactangle = new Rectangle2D.Double(x,y,width,height);
		g.draw(reactangle);
	}
	//填充矩形的辅助类
	public static void fillReactangle(Graphics2D g,int x,int y,int width,int height){
		Rectangle2D reactangle = new Rectangle2D.Double(x,y,width,height);
		g.fill(reactangle);
	}
	//设置画笔颜色
    public static void setColor(Graphics2D g,Color color){
    	g.setColor(color);
    }
    //设置边的宽度和平滑性
    public static void setStrokeWidth(Graphics2D g,int w){
    	//BasicStroke.CAP_ROUND把定点变为圆弧半径为画笔一半
    	//BasicStroke.JOIN_ROUND把线条连接点变为弧形
    	g.setStroke(new BasicStroke(w,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND));
    }
    //设置抗锯齿
    public static void setRenderingHints(Graphics2D g){
    	RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
    	hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
    	g.addRenderingHints(hints);
    }
    public static void pause(long millsecond){
    	try {
			Thread.sleep(20);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
    }
    //画图
    public static void drawImage(Graphics2D g,String imageUrl,int x,int y,int x1,int y1){
    	ImageIcon imageIcon=new ImageIcon(imageUrl);
    	Image image=imageIcon.getImage();
    	g.drawImage(image,x, y,x1,y1,null);
    }
    
}

运行效果

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值