看得见的算法——扫雷
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();
}
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)
下面是图片
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190717221604821.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190717221657792.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190717221725223.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190717221815243.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190717221556679.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190717221622372.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190717221846386.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190717221632632.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190717221709640.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190717221751718.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190717221804103.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190717221832409.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){
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);
}
}
运行效果
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190717225101243.gif)