看得見的算法——掃雷

看得見的算法——掃雷

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);
    }
    
}

運行效果

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章