- 介绍
- 基本思路
- 主要代码
- 代码地址
介绍
- 可以双击排雷;
- 无JButton,纯绘制;
- 游戏结束后显示历次时间排行榜;
- 第一步至少至少可以翻开九个格子,利于开局;
- 游戏难度易于调节,只需要改格子宽度和格子雷数之比即可,格子数量会根据屏幕高宽和格子宽度自动计算;
- 雷分布情况、每个格子翻开情况以及被标记情况均用二维数组存储;
- 系统在第一次翻开格子之后才开始布雷,要保证第一个格子及其周围八个格子不是雷;
- 每次鼠标事件,根据点击的相对位置来判断点中格子的坐标;
- 每次重绘,对于每个格子,根据其不同的状态在相应位置绘制不同的内容;
- 在最外圈单独设置一层非雷格子,在计算每个格子的周围雷数时不用考虑特殊情况,在绘制面板时,只需要绘制红色部分的格子即可,如下图;
Panel.java
public class Panel extends JPanel implements KeyListener{
private BufferedImage image;
private Graphics2D g2d;// 画笔
public double timer = 0;// 计时器
// 雷分布,周围雷数,翻开状态,被玩家标记状态
private int[][] mine,mineAround,isOpen,marked;
private static final int cellWidth = 90;// 格子宽度
private static final int difficulty = 6;// 难度系数(格子数与雷数比值)
private static final int barHeight = 50;// 顶栏高度
private static int xNum,yNum;// 横纵向格子数
private static int mineNum;// 雷数
private int leftMine = mineNum;// 剩余雷数
private int clickX,clickY,backHeight,backWidth;// 当前鼠标点中的格子坐标,面板高宽
public boolean start,finish,win;// 是否开始,是否结束,结束后是否胜利
private Image mineImg;// 雷图片
public Panel(){
// 初始化面板高宽,高度减去任务栏高度
backHeight = Frame.screenHeight-100;
backWidth = Frame.screenWidth;
// 根据格子宽度计算横纵格子数
xNum = backWidth/cellWidth;
yNum = (backHeight-barHeight)/cellWidth;
// 根据难度系数计算雷数
mineNum = xNum*yNum/difficulty;
// 雷图片
mineImg = new ImageIcon("resource/mine.png").getImage();
start = false;
finish = false;
win = false;
mine = new int[xNum+2][yNum+2];
mineAround = new int[xNum+2][yNum+2];
isOpen = new int [xNum+2][yNum+2];
marked = new int [xNum+2][yNum+2];
// 初始化画布
image = new BufferedImage(backWidth, backHeight, BufferedImage.TYPE_INT_BGR);
// 初始化画笔
g2d = image.createGraphics();
// 初始化背景色
g2d.setColor(new Color(200,200,200));
g2d.fillRect(0, 0, backWidth, backHeight);
// 初始化线条
g2d.setColor(new Color(55,55,55));
for (int i = 0; i <= yNum; i++) {
g2d.drawLine(0, cellWidth*i+barHeight, xNum*cellWidth, cellWidth*i+barHeight);
}
for (int i = 0; i <= xNum; i++) {
g2d.drawLine(cellWidth*i, barHeight, cellWidth*i, yNum*cellWidth+barHeight);
}
// 面板监听
this.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if(e.getClickCount()==2&&e.getButton()==e.BUTTON1) {
DoblueClick(e);// 双击
}else if(e.getButton()==e.BUTTON1){
LeftClick(e);// 左键
}else if(e.getButton()==e.BUTTON3){
RightClick(e);// 右键
}
}
});
Score.init();// 读取分数
new FreshThread(this).start();// 线程启动
}
protected void paintComponent(Graphics g){
super.paintComponent(g);
g.drawImage(image, 0, 0, this);//绘制画底
// 绘制时间和剩余雷数
g.setColor(Color.red);
g.setFont(new Font(null,Font.BOLD,28));
g.drawString(String.valueOf("时间:"+timer/1000+"s"), backWidth-300,40);
g.drawString(String.valueOf("剩余雷数:"+leftMine+"个"), backWidth-600,40);
g.setFont(new Font(null,Font.BOLD,cellWidth));
for(int i = 1; i <= xNum; i++){
for(int j = 1; j <= yNum; j++){
// 当前格子起始绘图坐标
int paintX = (i-1)*cellWidth+1;
int paintY = (j-1)*cellWidth+barHeight+1;
// 如果格子被翻开,则在对应位置绘制白色背景
if(isOpen[i][j]==1){
g.setColor(Color.white);
g.fillRect(paintX, paintY, cellWidth-2,cellWidth-2);
// 如果翻开的格子周围无雷,则不绘制周围雷数
if(mineAround[i][j]!=0){
if(mineAround[i][j]==1){
g.setColor(Color.pink);
}else if(mineAround[i][j]==2){
g.setColor(Color.orange);
}else if(mineAround[i][j]==3){
g.setColor(Color.red);
}else if(mineAround[i][j]==4){
g.setColor(new Color(102,0,0));
}else{// mineAround[i][j]==5,6,7,8
g.setColor(Color.black);
}
g.drawString(String.valueOf(mineAround[i][j]), (i-1)*cellWidth+10, j*cellWidth+barHeight-7);
}
}
// 如果格子被玩家标记,则绘制雷图
if(marked[i][j]==1){
g.drawImage(mineImg, paintX, paintY, cellWidth-1, cellWidth-1, null);
}
}
}
// 判断游戏是否胜利
if(judgeWin()){
//保存当前时间
Score.addNewScore(timer/1000);
win = true;
finish = true;
}
// 如果游戏开始则计时器累加
timer += start?FreshThread.FREASH:0;
}
// 获取每次鼠标点击格子的坐标,未点中格子则返回false
public boolean mouseClickXY(MouseEvent e){
for(int i = 1; i <= xNum; i++){
for(int j = 1; j <= yNum; j++){
if(new Rectangle((i-1)*cellWidth,(j-1)*cellWidth+barHeight,cellWidth,cellWidth).contains(e.getPoint())){
clickX = i;
clickY = j;
return true;
}
}
}
return false;
}
public int getRandom(int sample){
return (int)(Math.random()*sample+1);
}
// 计算每个格子周围的雷数/被玩家标记的雷数
public int calNeibor(int i,int j,int arr[][]){
int val = 0;
val+=arr[i-1][j];
val+=arr[i-1][j-1];
val+=arr[i][j-1];
val+=arr[i+1][j-1];
val+=arr[i+1][j];
val+=arr[i+1][j+1];
val+=arr[i][j+1];
val+=arr[i-1][j+1];
return val;
}
public void openCell(int x,int y){
// 不翻开被玩家标记的格子和已经翻开的格子
if(isOpen[x][y]==0&&marked[x][y]==0){
// 不翻开边界格子
if(x!=0&&x!=xNum+1&&y!=0&&y!=yNum+1){
// 翻开
isOpen[x][y]=1;
// 如果被翻开的格子周围无雷,则翻开其周围八个格子,以此类推
if(mineAround[x][y]==0){
openCell(x-1,y-1);
openCell(x-1,y);
openCell(x-1,y+1);
openCell(x,y-1);
openCell(x,y+1);
openCell(x+1,y-1);
openCell(x+1,y);
openCell(x+1,y+1);
}
}
}
}
// 布雷(第一次翻开之后)
public void setMine(MouseEvent e){
// 获取第一个格子的坐标
if(mouseClickXY(e)){
int index = 0;
// 循环随机生成雷
while(index
int tempX = getRandom(xNum);
int tempY = getRandom(yNum);
boolean flag = true;
// 判断生成的雷坐标是否位于第一个格子的周围,以及此位置是否已经有雷
if(judgeMineSelect(tempX,tempY)||(mine[tempX][tempY]==1)){
flag = false;
}
if(flag){
mine[tempX][tempY]=1;
index = index+1;
}
}
// 计算非雷格子的周围雷数
for(int i = 1; i <= xNum; i++){
for(int j = 1; j <= yNum; j++){
if(mine[i][j]==0){
mineAround[i][j]=calNeibor(i,j,mine);
}
}
}
// 输出雷分布情况
for(int j = 0 ;j
for(int i = 0; i
System.out.print(mine[i][j]+" ");
}
System.out.println();
}
// 翻开第一个格子
openCell(clickX,clickY);
// 游戏开始
start = true;
}
}
// 布雷时,判断随机生成雷的位置是否为第一个格子或在第一个格子的周围
public boolean judgeMineSelect(int tempX,int tempY){
if(tempX==clickX-1||tempX==clickX||tempX==clickX+1){
if(tempY==clickY-1||tempY==clickY||tempY==clickY+1){
return true;
}
}
return false;
}
public void LeftClick(MouseEvent e){
if(mouseClickXY(e)){
if(start){
// 被玩家标记的格子无法点击
if(isOpen[clickX][clickY]==0&&marked[clickX][clickY]==0){
if(mine[clickX][clickY]==1){
finish = true;
}else{
openCell(clickX,clickY);
}
}
}else{
setMine(e);
}
}
}
public void RightClick(MouseEvent e){
// 游戏未开始时不可标记
if(mouseClickXY(e)&&start&&isOpen[clickX][clickY]==0){
if(marked[clickX][clickY]==0){
// 如果剩余雷数小于等于0,无法标记
if(leftMine>0){
marked[clickX][clickY]=1;
leftMine-=1;
}
}else{
marked[clickX][clickY]=0;
leftMine+=1;
}
}
}
// 双击排雷
public void DoblueClick(MouseEvent e){
if(mouseClickXY(e)){
int i = clickX;
int j = clickY;
if(start){
if(isOpen[i][j]==1){
// 计算周围被玩家标记的雷数
int markedMine = calNeibor(i,j,marked);
// 如果周围标记数大于周围雷数,游戏结束
if(markedMine>mineAround[i][j]){
finish = true;
}else if(markedMine==mineAround[i][j]){
if(marked[i-1][j-1]==mine[i-1][j-1]&&
marked[i-1][j]==mine[i-1][j]&&
marked[i-1][j+1]==mine[i-1][j+1]&&
marked[i][j-1]==mine[i][j-1]&&
marked[i][j+1]==mine[i][j+1]&&
marked[i+1][j-1]==mine[i+1][j-1]&&
marked[i+1][j]==mine[i+1][j]&&
marked[i+1][j+1]==mine[i+1][j+1]
){
if(mine[i-1][j-1]==0)openCell(i-1,j-1);
if(mine[i-1][j]==0)openCell(i-1,j);
if(mine[i-1][j+1]==0)openCell(i-1,j+1);
if(mine[i][j-1]==0)openCell(i,j-1);
if(mine[i][j+1]==0)openCell(i,j+1);
if(mine[i+1][j-1]==0)openCell(i+1,j-1);
if(mine[i+1][j]==0)openCell(i+1,j);
if(mine[i+1][j+1]==0)openCell(i+1,j+1);
}else{
finish = true;
}
}
}
}else{
setMine(e);
}
}
}
// 判断是否胜利
public boolean judgeWin(){
// 如果雷未被玩家标记完,则未胜利
if(leftMine>0){
return false;
}
for(int i = 1; i <=xNum; i++){
for(int j = 1; j <= yNum; j++){
// 如果存在没被翻开也没被标记的格子,则未胜利
if(isOpen[i][j]==0&&marked[i][j]==0){
return false;
}
}
}
return true;
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
}
}
代码地址
gitee:https://gitee.com/meng-chenxu/java-minesweeping.



