一、项目介绍
扫雷是一款大众类的益智小游戏。根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。这款游戏有着很长的历史,从扫雷被开发出来到现在进行了无数次的优化,这款游戏通过简单的玩法,加上一个好看的游戏界面,每一处的细节都体现了扫雷的魅力。
项目采用技术
二维数组
文件读取存储
功能需求分析
完成 难度选择,雷随机生成,数字生成,用户登录与注册等功能实现
游戏四种状态: 难度选择、游戏状态、游戏胜利、游戏失败
游戏难度: 初级、中级、高级(不同难度对应不同的雷区大小和雷数量)
游戏核心: 二维数组 的相关操作
其他: 窗口绘制、界面规划、操作计数、重新开始。
项目亮点
1.登陆注册用到了文件的读取和存储
2.用二维数组来储存信息
3.将容器划分为网格,每个网格中可以放置一个组件,再对组件进行处理
系统演示图片
登录
注册图片
切换难度
游戏失败
游戏胜利
核心代码
文件的存储和读取
public void mouseClicked(MouseEvent t) {
if (t.getSource() == submit) {
//注册
if (username.getText().length() == 0 || password.getText().length() == 0 || rePassword.getText().length() == 0) {
showDialog("用户名和密码不能为空");
return;
}
if (!password.getText().equals(rePassword.getText())) {
showDialog("两次密码不一致");
return;
}
if (!username.getText().matches("[a-zA-Z0-9]{3,16}")) {
showDialog("用户名不符合规则");
return;
}
if (!password.getText().matches("[a-zA-Z0-9]{3,16}")) {
showDialog("密码不符合规则");
return;
}
if (containsUsername(username.getText())) {
showDialog("用户名已存在,重新输入");
return;
}
//添加用户
allUsers.add(new User(username.getText(), password.getText()));
//写入文件
BufferedWriter bw = null;
try {
//创建输出缓冲流对象
bw = new BufferedWriter(new FileWriter("C:\\Users\\86151\\IdeaProjects\\untitled1\\42\\src\\user.txt"));
for (User u : allUsers) {
bw.write(u.toString());
bw.newLine();
bw.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//释放资源
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//提醒注册成功
showDialog("注册成功");
//关闭注册界面,打开登陆界面
this.setVisible(false);
new LoginJFrame();
}
else if (t.getSource() == reset) {
//重置
username.setText("");
password.setText("");
rePassword.setText("");
}
}
=====================================================================
private void readUser(){
//读取数据
// 创建了一个 BufferedReader 对象,并将其与一个 FileReader 对象关联。
BufferedReader reader = null;
// 创建了一个 List 集合,
List<String> list = new ArrayList<>();
try {
reader = new BufferedReader(new FileReader("C:\\Users\\86151\\IdeaProjects\\untitled1\\42\\src\\user.txt"));
String line;
// 使用 BufferedReader 类的 readLine() 方法来一行一行地读取文件中的内容。
while ((line = reader.readLine()) != null) {
// 对于每一行,我们都将其添加到 List 集合中
list.add(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//遍历集合获取用户信息
for(String str:list){
//username=zhangsan & password=123
String []userIn=str.split("&");
String[] list1=userIn[0].split("=");
String[] list2=userIn[1].split("=");
User u=new User(list1[1],list2[1]);
allUsers.add(u);
}
}
布雷
public void layMinesForBlock(Block [][] block,int mineCount){ //在雷区布置mineCount个雷
initBlock(block); //先都设置是无雷
int row=block.length;
int column=block[0].length;
LinkedList<Block> list=new LinkedList<Block>();
for(int i=0;i<row;i++) {
for(int j=0;j<column;j++)
list.add(block[i][j]);
}
while(mineCount>0){//开始布雷
//它首先获取列表的大小,并生成一个随机索引值。
int size=list.size(); // list返回节点的个数
int randomIndex=(int)(Math.random()*size);
// 然后,它使用这个索引值从列表中获取一个方块对象,并将其 isMine 属性设置为 true,
Block b=list.get(randomIndex);
b.setIsMine(true);
// 将其名称设置为 "雷",并将其图标设置为 mineIcon。
b.setName("雷");
b.setMineIcon(mineIcon);
// 最后,它从列表中删除这个方块对象,并将地雷计数器减一。
list.remove(randomIndex);
mineCount--;
// 当地雷计数器减为零时,循环结束。
}
for(int i=0;i<row;i++){ //检查布雷情况,标记每个方块周围的雷的数目
for(int j=0;j<column;j++){
if(block[i][j].isMine()){
block[i][j].setIsOpen(false);
block[i][j].setIsMark(false);
}
else {
//计算一个二维数组 block 中某个元素周围的地雷数量。
// 它首先定义了一个变量 mineNumber 并将其初始值设置为 0。
int mineNumber=0;
// 然后,它使用两个嵌套循环遍历该元素周围的所有元素
for(int k=Math.max(i-1,0);k<=Math.min(i+1,row-1);k++) {
for(int t=Math.max(j-1,0);t<=Math.min(j+1,column-1);t++){
// 对于每一个元素,如果它是地雷(即 block[k][t].isMine() 返回 true),则将 mineNumber 的值加一。
if(block[k][t].isMine())
mineNumber++;
}
}
// 循环结束后,mineNumber 的值就是该元素周围的地雷数量。
block[i][j].setIsOpen(false);
block[i][j].setIsMark(false);
block[i][j].setName(""+mineNumber);
block[i][j].setAroundMineNumber(mineNumber); //设置该方块周围的雷数目
}
}
}
}
得到所点块周围不是雷的方块
public Stack<Block> getNoMineAroundBlock(Block bk){//得到方块bk附近区域不是雷的方块
// 清空栈 notMineBlock。
notMineBlock.clear();
// 然后,它使用两个嵌套循环遍历二维数组中的每一个元素,寻找与参数 bk 相同的元素。
for(int i=0;i<row;i++) { //寻找bk在雷区block中的位置索引
for(int j=0;j<colum;j++) {
// 如果找到了这样的元素,则将其索引值分别赋给类的成员变量 m 和 n。
if(bk == block[i][j]){
m=i;
n=j;
break;
}
}
}
if(!bk.isMine()) { //方块不是雷
show(m,n); //见后面的递归方法
}
return notMineBlock;
}
public void show(int m,int n) {
//首先检查二维数组中索引为 (m, n) 的元素周围是否有地雷。
if(block[m][n].getAroundMineNumber()>0&&block[m][n].getIsOpen()==false){
block[m][n].setIsOpen(true);
notMineBlock.push(block[m][n]); //将不是雷的方块压栈
return;
}
else if(block[m][n].getAroundMineNumber()==0&&block[m][n].getIsOpen()==false){
// 则将该元素的 isOpen 属性设置为 true
block[m][n].setIsOpen(true);
notMineBlock.push(block[m][n]);
//将不是雷的方块压栈
// 然后,使用两个嵌套循环遍历该元素周围的所有元素,并对每一个元素递归调用本方法。
for(int k=Math.max(m-1,0);k<=Math.min(m+1,row-1);k++) {
for(int t=Math.max(n-1,0);t<=Math.min(n+1,colum-1);t++)
show(k,t);
}
}
}
胜利
public boolean verifyWin(){
//这个方法检查玩家是否获胜。
// 它首先定义了一个布尔变量 isOK 并将其初始值设置为 false
// 然后,它定义了一个整数变量 number 并将其初始值设置为 0。
boolean isOK = false;
int number=0;
// 接下来,它使用两个嵌套循环遍历二维数组中的每一个元素。
for(int i=0;i<row;i++) {
for(int j=0;j<colum;j++) {
// 对于每一个元素,如果它的 isOpen 属性为 false,则将 number 的值加一。
if(block[i][j].getIsOpen()==false)
number++;
}
}
// 循环结束后,如果 number 的值等于地雷数量(即类的成员变量 mineCount 的值),
// 则将 isOK 的值设置为 true。最后,返回 isOK 的值。
if(number==mineCount){
isOK =true;
}
return isOK;
}
设置方块信息
public BlockView(){
card=new CardLayout();
//设置卡片式为面板的布局管理器。
setLayout(card);
//第一行代码创建了一个新的JLabel对象。它的文本内容为空字符串,文本的水平对齐方式为居中。
blockNameOrIcon=new JLabel("",JLabel.CENTER);
//第二行代码设置了标签上文本的水平对齐方式。它将文本的水平对齐方式设置为居中。
blockNameOrIcon.setHorizontalTextPosition(AbstractButton.CENTER);
//第三行代码设置了标签上文本的垂直对齐方式。它将文本的垂直对齐方式设置为居中。
blockNameOrIcon.setVerticalTextPosition(AbstractButton.CENTER);
blockCover=new JButton();
add("cover",blockCover);
add("view",blockNameOrIcon);
}
public void acceptBlock(Block block){
this.block = block;
}
public void setDataOnView(){
//这个方法根据类的成员变量 block 的属性来设置标签的文本和图标。
if(block.isMine()){
// 将标签的文本设置为方块的名称,并将标签的图标设置为地雷图标。
blockNameOrIcon.setText(block.getName());
blockNameOrIcon.setIcon(block.getMineicon());
}
else {
// 否则,如果方块周围有地雷,则将标签的文本设置为方块周围地雷数量的字符串表示形式。
int n=block.getAroundMineNumber();
if(n>=1)
blockNameOrIcon.setText(""+n);
else
// 将标签的文本设置为空格。
blockNameOrIcon.setText(" ");
}
}
//当玩家点击一个方块时,游戏会调用类的成员方法 seeBlockNameOrIcon() 来显示标签,
// 从而让玩家看到方块的信息。
public void seeBlockNameOrIcon(){
card.show(this,"view");
validate();
}
//当玩家重新开始游戏时,游戏会调用类的成员方法 seeBlockCover() 来显示按钮,从而遮挡标签。
public void seeBlockCover(){
card.show(this,"cover");
validate();
}
public JButton getBlockCover(){
return blockCover;
}
初始化雷区
public void initMineArea(int row,int colum,int mineCount,String grade){
//它首先清空面板 pCenter 中的所有组件,并将类的成员变量的值设置为相应的参数值。
pCenter.removeAll();
spendTime=0;
markMount=mineCount;
this.row=row;
this.colum=colum;
this.mineCount=mineCount;
this.grade=grade;
// 然后,它创建了一个二维数组,并使用两个嵌套循环遍历这个数组中的每一个元素。
block=new Block[row][colum];
for(int i=0;i<row;i++){
for(int j=0;j<colum;j++)
// 对于每一个元素,它创建了一个新的 Block 对象并将其存储在数组中。
block[i][j]=new Block();
}
// 接下来,它调用布雷者对象的成员方法来布置地雷,并调用扫雷者对象的成员方法来准备扫雷。
lay.layMinesForBlock(block,mineCount); //布雷
peopleScoutMine.setBlock(block,mineCount); //准备扫雷
blockView=new BlockView[row][colum]; //创建方块的视图
//GridLayout布局管理器将容器划分为网格,每个网格中可以放置一个组件。
// 指定网格的行数和列数。
pCenter.setLayout(new GridLayout(row,colum));
// 然后,它创建了一个二维数组,并使用两个嵌套循环遍历这个数组中的每一个元素。
// 对于每一个元素,它创建了一个新的 BlockView 视图对象并将其存储在数组中。
for(int i=0;i<row;i++) {
for(int j=0;j<colum;j++) {
blockView[i][j]=new BlockView();
block[i][j].setBlockView(blockView[i][j]); //方块设置自己的视图
// 调用视图对象的成员方法来将block[i][j]的数据放入视图中。
blockView[i][j].setDataOnView();
// 接下来,它将视图对象添加到面板 pCenter 中,
// 并为视图对象上的按钮注册了一个动作监听器和一个鼠标监听器。
pCenter.add(blockView[i][j]);
blockView[i][j].getBlockCover().addActionListener(this);//注册监视器
blockView[i][j].getBlockCover().addMouseListener(this);
blockView[i][j].seeBlockCover(); //初始时盖住block[i][j]的数据信息
//将按钮设置为可用状态。
blockView[i][j].getBlockCover().setEnabled(true);
//按钮将显示这个图标,而不是文本。
blockView[i][j].getBlockCover().setIcon(null);
}
}
//将探明的雷数添加到文本中
showMarkedMineCount.setText(""+markMount);
//重绘面板。
repaint();
}
点击方块
public void actionPerformed(ActionEvent e) {
// 如果方块是地雷,则调用视图对象的成员方法来显示标签。
//如果事件源是重新开始按钮,则调用类的成员方法来初始化雷区,并重绘面板。
//如果事件源是计时器,则将用时增加 1,并更新文本字段 showTime 的文本。
//最后,检查玩家是否获胜。如果获胜,则停止计时器并显示提示框。
if(e.getSource()!=reStart&&e.getSource()!=time) {
//开始计时
time.start();
int m=-1,n=-1;
for(int i=0;i<row;i++) { //找到单击的方块以及它的位置索引
for(int j=0;j<colum;j++) {
if(e.getSource()==blockView[i][j].getBlockCover()){
m=i;
n=j;
break;
}
}
}
//检查该方块是否是地雷。
if(block[m][n].isMine()) { //用户输掉游戏
//遍历二维数组中的每一个元素,并将每一个方块的按钮设置为不可用状态。
for(int i=0;i<row;i++) {
for(int j=0;j<colum;j++) {
blockView[i][j].getBlockCover().setEnabled(false);//用户单击无效了
if(block[i][j].isMine())
blockView[i][j].seeBlockNameOrIcon(); //视图显示方块上的数据信息
}
}
time.stop();
spendTime=0; //恢复初始值
markMount=mineCount; //恢复初始值
}
else {
//遍历周围没有地雷的方块,并显示它们上面的数据信息。
//首先调用peopleScoutMine.getNoMineAroundBlock(block[m][n])方法获取周围没有地雷的方块,并将它们存储在notMineBlock栈中。
Stack<Block> notMineBlock =peopleScoutMine.getNoMineAroundBlock(block[m][n]);
while(!notMineBlock.empty()){
//在每次循环中,它使用notMineBlock.pop()方法移除栈顶元素并返回它。。
Block bk = notMineBlock.pop();
//接下来,它调用bk.getBlockView()方法获取该方块的视图对象,并将其赋值给变量viewforBlock。
ViewForBlock viewforBlock = bk.getBlockView();
//最后,它调用viewforBlock.seeBlockNameOrIcon()方法显示方块上的数据信息。
viewforBlock.seeBlockNameOrIcon();
}
}
}
//如果事件源是重新开始按钮,则调用类的成员方法来初始化雷区,并重绘面板。
if(e.getSource()==reStart) {
initMineArea(row,colum,mineCount,grade);
repaint();
validate();
}
//如果点击计时器,则将用时增加 1,并更新文本字段 showTime 的文本。
if(e.getSource()==time){
spendTime++;
showTime.setText(""+spendTime);
}
if(peopleScoutMine.verifyWin()) { //判断用户是否扫雷成功
//则停止计时器并显示提示框
showDialog("你胜利了");
spendTime=0;
}
}
右键标记
public void mousePressed(MouseEvent e){ //探雷:给方块上插一个小旗图标(再次单击取消)
// 当用户按下鼠标右键时,这个方法会被调用。
//e.getSource()方法返回了触发事件的组件。强转将其转换为JButton类型,并将其赋值给变量source
JButton source=(JButton)e.getSource();
for(int i=0;i<row;i++) {
for(int j=0;j<colum;j++) {
// 对于每一个元素,如果事件源与该元素上的按钮相同,则检查该元素是否已经被标记。
//1.检查鼠标事件的修饰符是否为InputEvent.BUTTON3_MASK。如果是,则表示用户按下了鼠标右键。
//2.检查事件源(即被单击的按钮)是否与blockView[i][j]元素上的按钮相同。
if(e.getModifiers()==InputEvent.BUTTON3_MASK&&
source==blockView[i][j].getBlockCover()){
if(block[i][j].getIsMark()) {
//将按钮上的图标设置为null。这样,按钮上原先显示的图标就会消失。
source.setIcon(null);
//将方块的isMark属性设置为false。这样,该方块就不再被标记为有地雷。
block[i][j].setIsMark(false);
markMount=markMount+1;
//更新文本
showMarkedMineCount.setText(""+markMount);
}
else{
// 标记该元素
source.setIcon(mark);
block[i][j].setIsMark(true);
markMount=markMount-1;
showMarkedMineCount.setText(""+markMount);
}
}
}
}
}
成员 | 负责模块 |
杨德旭 | 雷的标记,方块点击,初始化雷区,界面搭建 |
郭延博 | 雷的布置,胜利判断,显示扫雷游戏中的方块 |
github地址:
https://gitee.com/yaun_123/minesweeping-mini-program-1
github提交照片: