随风而动,随遇而安......
Java实现扫雷小游戏二
布雷
上一篇已经完成了界面UI的实现,接下来开始功能的实现。
定义布雷类(com.tools/LayMine.java)
(1)布雷
采用随机生成的布雷方式,玩家第一次点击小方格不应该是雷,故布雷功能设计在玩家第一次左键时开始布雷。参数row和col是第一次点击的鼠标坐标。
public class LayMine {
/**
* labels:存储方格的二维数据
* row:当前鼠标点击的x值
* col:当前鼠标点击的y值
*/
public static void lay(MineLabel[][] labels, int row, int col) {
int count = 0;
Random random = new Random(); // 随机
while (count<Tools.allcount) {
int x = random.nextInt(Tools.rows);
int y = random.nextInt(Tools.cols);
if(!labels[x][y].isMineTag() && (x !=row && y!= col)) {
// 布雷
labels[x][y].setMineTag(true);
count++;
}
}
countBomb(labels);
}
(2)计算小方格周围雷的数量
分析当前方格(x,y)周围方格的坐标:设 x 和 y的最大值分别为 X 和 Y :
- (
[0, x+1]
,[0, y+1]
) - (
[x-1, x+1]
,[0, y+1]
) - (
[x-1, X]
,[0, y+1]
) - (
[0, x+1]
,[y-1, y+1]
) - (
[x-1, x+1]
,[y-1, y+1]
) - (
[x-1, X]
,[y-1, y+1]
) - (
[0, x+1]
,[y-1, Y]
) - (
[x-1, x+1]
,[y-1, Y]
) - (
[x-1, X]
,[y-1, Y]
)
当前点(x,y)的周围方格
- x值最小值或者为0,或者为x-1,且不能小于0,
- x的最小值取Math.max(0, x- 1),x的最大值x+1,或者为X,且x+1最大不能超过X,
故x的范围:
Math.max(0, x - 1) 至 Math.min(Tools.rows - 1, x + 1)
同理y的范围:
Math.max(0, y - 1) 至 Math.min(Tools.cols - 1, y + 1)
实现方法一
理解简单,通俗易懂,代码执行效率较低,不推荐使用
public static void countBomb(MineLabel[][] labels) {
int count = 0;
if (!mineLabel[i][j].isMine()) {
// 计算雷块周围八个方向雷数
/**
* 上
*/
if (i > 0) {
if (labels[i - 1][j].isMine()) {
count++;
}
}
/**
* 左上
*/
if (i > 0 && j>0) {
if (labels[i - 1][j-1].isMine()) {
count++;
}
}
/**
* 右上
*/
if (i > 0&&j+1< Tools.cols) {
if (labels[i - 1][j+1].isMine()) {
count++;
}
}
/**
* 左
*/
if (j>0) {
if (labels[i][j-1].isMine()) {
count++;
}
}
/**
* 右
*/
if (j+1< Tools.cols) {
if (labels[i][j+1].isMine()) {
count++;
}
}
/**
* 左下
*/
if (i+1< Tools.rows && j>0) {
if (labels[i + 1][j-1].isMine()) {
count++;
}
}
/**
* 下
*/
if (i+1< Tools.rows) {
if (labels[i + 1][j].isMine()) {
count++;
}
}
/**
* 右下
*/
if (i+1< Tools.rows && j+1< Tools.cols) {
if (labels[i + 1][j+1].isMine()) {
count++;
}
}
}
}
实现方法二
/**
* |计算周围雷数
*/
public static void countBomb(MineLabel[][] labels) {
int count = 0;
for (int i = 0; i < Tools.rows; i++) {
for (int j = 0; j < Tools.cols; j++) {
count = 0;
// 当前方格不是雷才计算周围雷的数量
if (!labels[i][j].isMineTag()) {
for (int x = Math.max(i - 1, 0); x <= Math.min(i + 1, Tools.rows - 1); x++) {
for (int y = Math.max(j - 1, 0); y <= Math.min(j + 1, Tools.cols - 1); y++) {
if (labels[x][y].isMineTag()) {
count++;
}
}
}
labels[i][j].setCountAround(count);
}
}
}
}
编写测试类(test/TestBomb.java)
测试布雷和计数是否准确
public class TestBomb {
/**
* 测试类
*/
public static void main(String[] args) {
MainFrame mainframe = new MainFrame();
MineLabel[][] labels = mainframe.getBombJPanel().getLabels();
LayMine.lay(labels, 3, 3); // 假设当前鼠标点击位置【3,3】
LayMine.countBomb(labels);
for (int i = 0; i < Tools.rows; i++) {
for (int j = 0; j < Tools.cols; j++) {
if (labels[i][j].isMineTag()) {
labels[i][j].setIcon(Tools.mine);
}else {
int count = labels[i][j].getCountAround();
labels[i][j].setIcon(Tools.mineCount[count]);
}
}
}
}
}
运行效果
鼠标事件监听
添加鼠标事件监听(com.listener/MouListener)
public class MouListener implements MouseListener {
MainFrame mainframe;
MineLabel[][] labels;
Boolean isDoublePress = false;;
public MouListener(MineLabel[][] labels, MainFrame mainframe) {
this.labels = labels;
this.mainframe = mainframe;
}
在 BombJPanel.java 中为雷区小方块添加事件监听
public BombJPanel(MainFrame mainframe) {
this.mainframe = mainframe;
//定义布局方式,网格布局
this.setLayout(new GridLayout(Tools.rows, Tools.cols));
listener = new MouListener(labels,mainframe);
init();
}
// 初始化
private void init() {
// 实例化小方格
for(int i = 0;i<labels.length;i++) {
for(int j = 0; j<labels[i].length;j++) {
labels[i][j] = new MineLabel(i, j);
labels[i][j].setIcon(Tools.blank);
this.add(labels[i][j]);
labels[i][j].addMouseListener(listener);
}
}
// 实现边框效果
Border lowerBorder = BorderFactory.createLoweredBevelBorder();
Border emptyBorder = BorderFactory.createEmptyBorder(5, 5, 5, 5); //边框大小
CompoundBorder compoundBorder = BorderFactory.createCompoundBorder(emptyBorder, lowerBorder) ;
this.setBorder(compoundBorder);
this.setBackground(Color.LIGHT_GRAY);
}
}
需求分析:鼠标操作包含:左键、右键、左右键同时按下、右键一次、右键两次、右键三次。
鼠标按下
鼠标左键按下时效果:
(1)设置笑脸为惊叹
(2)左右键同时按下:设置鼠标所在位置周围小方格为背景效果
左键按下时效果:
(1)设置笑脸为惊叹
(2)如果未被展开的,则显示鼠标所在位置的小方格的背景
(3)已被展开则不做处理
鼠标右键按下时效果:
(1)第一次按下:标记插上红旗
(2)第二次按下:标记显示问号
(3)第三次按下:还原
// 鼠标按下时
@Override
public void mousePressed(MouseEvent e) {
MineLabel mineLabel = (MineLabel) e.getSource(); // 获取事件源
int row = mineLabel.getRowx();
int col = mineLabel.getColy();
// 判断是否是鼠标双击(左右键)操作
if (e.getModifiersEx() == InputEvent.BUTTON1_DOWN_MASK + InputEvent.BUTTON3_DOWN_MASK) {
isDoublePress = true;
doublePress(row, col);
// 鼠标左键按下事件
} else if (e.getModifiers() == InputEvent.BUTTON1_MASK && !mineLabel.isFlagTag()) {
// 对没有被展开或标记的方格
if (!mineLabel.isExpendTag()) {
// 鼠标左键按下背景
mineLabel.setIcon(Tools.mineCount[0]);
}
// 表情变惊讶
mainframe.getFaceJPanel().getLabelFace().setIcon(Tools.face2);
// 鼠标右键按下事件
} else if (e.getModifiers() == InputEvent.BUTTON3_MASK && !mineLabel.isExpendTag()) {
// 右键点击数为 0 时
if (mineLabel.getRightClickCount() == 0) {
mineLabel.setIcon(Tools.flag); // 设置旗子标记
mineLabel.setRightClickCount(1);
mineLabel.setFlagTag(true);
Tools.bombCount--;
mainframe.getFaceJPanel().setNumber(Tools.bombCount); // 改变计数区雷数图片
// 右键点击数为 1 时
} else if (mineLabel.getRightClickCount() == 1) {
mineLabel.setIcon(Tools.ask); // 设置问号标记
mineLabel.setRightClickCount(2);
mineLabel.setFlagTag(false);
Tools.bombCount++;
mainframe.getFaceJPanel().setNumber(Tools.bombCount); // 改变计数区雷数图片
// 第3次点击还原回 未标记状态
} else {
mineLabel.setIcon(Tools.blank);
mineLabel.setRightClickCount(0);
mineLabel.setFlagTag(false);
}
}
}
FaceJPanel.java类中添加:
// 计数器 根据当前旗子数计算剩余雷数
public void setNumber(int count) {
int b = 0;
if (count < 0) {
b = 10;
} else {
b = count / 100;
}
int g = Math.abs(count) % 10;
int s = Math.abs(count) / 10 % 10;
labelCountG.setIcon(Tools.timeCount[g]);
labelCountS.setIcon(Tools.timeCount[s]);
labelCountB.setIcon(Tools.timeCount[b]);
}
鼠标释放
需求分析:包含左右键双击释放,左键释放,鼠标右键弹起没有任何动作。
鼠标左右键双击释放
(1)如果当前方格(被点击的方格)没有标记,且之前未被展开,则还原成点击前的状态(外观);
(2)否则(已标记,或者已被展开),就判断方格周围雷的数量与周围被标记旗子的方格数是否相等,如果相等就展开周围的方格
A.相等的情况有标记存在两种情况:
- 标记正确:如下图对圆圈所在方格双击释放时将打开其周围的方格
- 标记错误:会有惩罚,相当于触雷,游戏结束。
即当前方格周围的雷全部并且正确标记,则会迅速打开当前方格周围未打开的方格,如果标记有错误,则进行惩罚
B.不相等:还原小方格状态
鼠标左键释放
(1)如果是第一次点击,则布雷,且点击的方格不布雷,确保第一次点击不触雷。
(2)如果踩到雷,则引发触雷,游戏结束
(3)否则展开方格
// 鼠标左右键同时按下
private void doublePress(int row, int col) {
for (int x = Math.max(0, row - 1); x <= Math.min(Tools.rows - 1, row + 1); x++) {
for (int y = Math.max(0, col - 1); y <= Math.min(Tools.cols - 1, col + 1); y++) {
if (!labels[x][y].isExpendTag() && !labels[x][y].isFlagTag()) {
int rightClickCount = labels[x][y].getRightClickCount();
// 对标记旗子或者展开的不做处理
if (rightClickCount == 1) {
labels[x][y].setIcon(Tools.flag);
} else {
labels[x][y].setIcon(Tools.mineCount[0]);
}
}
}
}
}
// 左键展开方格
private void expand(int x, int y) {
int count = labels[x][y].getCountAround();
if (!labels[x][y].isExpendTag() && !labels[x][y].isFlagTag()) {
// 周围雷为0,递归调用继续展开
if (count == 0) {
labels[x][y].setIcon(Tools.mineCount[count]);
labels[x][y].setExpendTag(true);
for (int i = Math.max(0, x - 1); i <= Math.min(Tools.rows -1, x + 1); i++) {
for (int j = Math.max(0, y - 1); j <= Math.min(Tools.cols -1, y + 1); j++) {
expand(i, j);
}
}
} else {
// 显示周围雷数
labels[x][y].setIcon(Tools.mineCount[count]);
labels[x][y].setExpendTag(true);
}
}
}
@Override
public void mouseReleased(MouseEvent e) {
MineLabel mineLabel= (MineLabel) e.getSource(); // 当前方格
int row = mineLabel.getRowx();
int col = mineLabel.getColy();
// 鼠标双击释放(右键不做处理)
if(isDoublePress) {
isDoublePress = false;
if (!mineLabel.isExpendTag() && !mineLabel.isFlagTag()) {
backIcon(row, col);
} else {
boolean isEquals = isEquals(row, col);
if (isEquals) {
doubleExpend(row, col);
} else {
backIcon(row, col);
}
}
mainframe.getFaceJPanel().getLabelFace().setIcon(Tools.face0);
// 左键释放
}else if (e.getModifiers() == InputEvent.BUTTON1_MASK && !mineLabel.isFlagTag()) {
if(!Tools.isStart) { // 第一次鼠标左键点击,在弹起事件中进行布雷
LayMine.lay(labels, row, col); // 布雷
Tools.isStart = true; //设置isStart=true,表示不是第一次点击了
mainframe.getTimer().start(); // 开启计时器
}
if (mineLabel.isMineTag()) {//判断是否踩到地雷
bombAction(row, col); //如果踩到地雷,游戏结束,显示全部的地雷
mineLabel.setIcon(Tools.blood);
mainframe.getFaceJPanel().getLabelFace().setIcon(Tools.face3);
} else {
mainframe.getFaceJPanel().getLabelFace().setIcon(Tools.face0);
expand(row, col);
}
}
//判断雷是否已全被清除完
isWin();
}
// 左右键双击展开
private void doubleExpend(int i, int j) {
for (int x = Math.max(0, i - 1); x <= Math.min(Tools.rows - 1, i + 1); x++) {
for (int y = Math.max(0, j - 1); y <= Math.min(Tools.cols - 1, j + 1); y++) {
if (labels[x][y].isMineTag()) { // 如果是雷
if (!labels[x][y].isFlagTag()) { // 没有旗子标记
bombAction(x, y);
}
} else { // 不是雷
if (!labels[x][y].isFlagTag()) { // 没有旗子标记
expand(x, y);
}
}
}
}
}
// 触雷
private void bombAction(int row, int col) {
for (int i = 0; i < labels.length; i++) {
for (int j = 0; j < labels[i].length; j++) {
if (labels[i][j].isMineTag()) { // 是雷
if (!labels[i][j].isFlagTag()) { // 没有标记
labels[i][j].setIcon(Tools.mine0);
} else {
labels[i][j].setIcon(Tools.mine1);
}
}
}
}
// 修改踩雷状态
Tools.isBoom = true;
// 停止计时器
mainframe.getTimer().stop();
// 取消鼠标监听器
for (int i = 0; i < labels.length; i++) {
for (int j = 0; j < labels[i].length; j++) {
labels[i][j].removeMouseListener(this);
}
}
}
//还原方格显示效果(因为鼠标按下时显示背景)
private void backIcon(int i, int j) {
for (int x = Math.max(0, i - 1); x <= Math.min(Tools.rows - 1, i + 1); x++) {
for (int y = Math.max(0, j - 1); y <= Math.min(Tools.cols - 1, j + 1); y++) {
if (!labels[x][y].isFlagTag() && !labels[x][y].isExpendTag()) {
int rightClickCount = labels[x][y].getRightClickCount();
if (rightClickCount == 2) {
labels[x][y].setIcon(Tools.ask);
} else {
if(!labels[x][y].isFlagTag()){
labels[x][y].setIcon(Tools.blank);
}
}
}
}
}
}
计数区面板点击事件
(1)计时(com.timer/Timers)
在主窗体中添加计时器timer控件,并编写事件监听器
// 计时器
public class Timers implements ActionListener {
private int times;
MainFrame mainfame;
public Timers(MainFrame mainfame){
this.mainfame = mainfame;
}
@Override
public void actionPerformed(ActionEvent arg0) {
// TODO Auto-generated method stub
Tools.timecount++;
if(Tools.timecount>999){
Tools.timecount=999;
}else{
mainfame.getFaceJPanel().setTime(Tools.timecount);
}
}
}
在FaceJPanel.java中添加:
// 计时器
public void setTime(int count) {
int b = 0;
if (count < 0) {
b = 10;
} else {
b = count / 100;
}
int g = Math.abs(count) % 10;
int s = Math.abs(count) / 10 % 10;
labelTimeG.setIcon(Tools.timeCount[g]);
labelTimeS.setIcon(Tools.timeCount[s]);
labelTimeB.setIcon(Tools.timeCount[b]);
}
(2)“笑脸”事件处理(com.panel/FaceJPanel.java)
public class FacelabelListener extends MouseAdapter{
@Override
public void mousePressed(MouseEvent e) {
if (e.getModifiers() == InputEvent.BUTTON1_MASK) {
mainframe.getTimer().stop();
labelFace.setIcon(Tools.face1);
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (e.getModifiers() == InputEvent.BUTTON1_MASK) {
mainframe.getTimer().start();
mainframe.reStartGame();
labelFace.setIcon(Tools.face0);
}
}
(3)重新开始方法(com.main/mainFrame.java)
BombJPanel bombJPanel = new BombJPanel(this);
FaceJPanel faceJPanel = new FaceJPanel(this);
public void reStartGame() {
// 游戏重新开始方法
this.remove(faceJPanel);
this.remove(bombJPanel);
Tools.bombCount = Tools.allcount;
Tools.timecount = 0;
Tools.isStart = false;
Tools.isBoom = false;
faceJPanel = new FaceJPanel(this);
bombJPanel = new BombJPanel(this);
this.add(faceJPanel, BorderLayout.NORTH);
this.add(bombJPanel);
this.pack();
this.validate();
getTimer().stop();
}
在的init方法中添加
private void init() {
// 菜单栏
this.setJMenuBar(menuBar);
BorderLayout layout = new BorderLayout();
this.setLayout(layout);
// 计数区
this.add(faceJPanel,layout.NORTH);
// 雷区
this.add(bombJPanel,layout.CENTER);
}
扫雷成功
需求分析:把不是雷的方格全部展开,如果不是雷的方格全部展开了,但雷没被标记也算扫雷成功,以下等式成立即可。
被展开的方格数量
= 所有方格数量
- 雷的数量
private void isWin() {
int expendCount = 0;
for (int i = 0; i < labels.length; i++) {
for (int j = 0; j < labels[i].length; j++) {
if (labels[i][j].isExpendTag()) {
expendCount++;
}
}
}
if (Tools.rows * Tools.cols - expendCount == Tools.allcount) {
for (int i = 0; i < Tools.rows; i++)
for (int j = 0; j < Tools.cols; j++) {
if (mainframe.getBombJPanel().getLabels()[i][j].isMineTag()
&& !mainframe.getBombJPanel().getLabels()[i][j].isFlagTag()) {
mainframe.getBombJPanel().getLabels()[i][j].setIcon(Tools.flag);
}
// 移除监听
mainframe.getBombJPanel().getLabels()[i][j]
.removeMouseListener(mainframe.getBombJPanel()
.getListener());
}
mainframe.getFaceJPanel().getLabelFace().setIcon(Tools.face4);
mainframe.getFaceJPanel().setNumber(0);
mainframe.getTimer().stop();
new Win(mainframe);
//成功后弹出英雄记录版
Tools.isStart = false;
}
}
//判断方格周围雷的数量与周围被标记的方格数是否相等
private boolean isEquals(int i, int j) {
int count = labels[i][j].getCountAround();
int flagCount = 0;
for (int x = Math.max(0, i - 1); x <= Math.min(Tools.rows - 1, i + 1); x++) {
for (int y = Math.max(0, j - 1); y <= Math.min(Tools.cols - 1, j + 1); y++) {
if (labels[x][y].isFlagTag()) {
flagCount++;
}
}
}
if (count == flagCount) {
return true;
}else {
return false;
}
}