You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

656 lines
28 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.*;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
public class Sao_lei extends JFrame {
private JButton[] box_x; // 格子
private JMenuItem mo_shi_1, mo_shi_2, mo_shi_3, viewLeaderboard;
private String currentUsername = ""; // 当前用户名
private JPanel box_center;
private JLabel label_nadu; // 难度标签
private JLabel label_timer; // 计时标签
private int rows, cols; // 地图的行数和列数
private int lei; // 地雷数量
private int[][] mineMap; // 用于存储地图信息的二维数组
private Timer timer; // 计时器
private int timeall; // 经过的时间
private int flagsPlaced; // 已放置的标记数量
private ImageIcon face; // 笑脸图片
private JButton label_face; // 笑脸按钮
private LeaderboardDialog leaderboardDialog; // 排行榜对话框
private static class Mysql_Set {//静态类//只能有一个实类(无需创建类实类)//减少内存//直接通过类名调用//避免多线程安全//不会被修改//
private static final String URL = "jdbc:mysql://localhost:3306/saolei?useSSL=false&serverTimezone=UTC";
private static final String USER = "root";
private static final String PASSWORD = "254689";
public static void INIT_Mysql() {
try{
Class.forName("com.mysql.cj.jdbc.Driver");//链接驱动包JDBC
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);//建立连接
String SQL_txt = "CREATE TABLE IF NOT EXISTS leaderboard (" +//创建 表 如果 不 存在 leaderboard//
"id INT AUTO_INCREMENT PRIMARY KEY," +//id 整形 自增 主建(不能为NULL不能重复)//
"username VARCHAR(50) NOT NULL," +//username 字符串(50) 不能为空//
"time_used INT NOT NULL," +//time_used 整形 不能为空//
"difficulty VARCHAR(10) NOT NULL," +//difficulty 字符串(10) 不能为空//
"completion_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP" +//completion_time 时间戳T 默认当前时间DC//
")";//结束
Statement stmt = conn.createStatement();//创建一个对象
stmt.execute(SQL_txt);//执行SQL语句
} catch (SQLException | ClassNotFoundException e) {
JOptionPane.showMessageDialog(null, "数据库连接失败:", "错误", JOptionPane.ERROR_MESSAGE);//弹出错误信息框
}
}
public static void ADD_Mysql(String username, int timeUsed, String difficulty) {//插入用户数据
try {
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);//建立连接
String sql = "INSERT INTO leaderboard (username, time_used, difficulty) VALUES (?,?,?)";//插入 进入 排行榜 值(?占位符防止攻击)
PreparedStatement pstmt = conn.prepareStatement(sql);//创建一个对象
pstmt.setString(1, username);//设置占位符
pstmt.setInt(2, timeUsed);//设置占位符
pstmt.setString(3, difficulty);//设置占位符
pstmt.executeUpdate();//执行SQL语句
} catch (SQLException e) {
JOptionPane.showMessageDialog(null, "保存分数失败:" , "错误", JOptionPane.ERROR_MESSAGE);
}
}
public static List<ScoreRecord> GET_Mysql_Top(String difficulty, int limit) {//获取排行榜//返回泛型集合类型
List<ScoreRecord> scores = new ArrayList<>();//定义数组列表Scores
String sql = "SELECT username, time_used, completion_time FROM leaderboard " +//选择 123 来自 表//
"WHERE difficulty = ? ORDER BY time_used ASC LIMIT ?";//哪里 难度 = 从 表 排序 用时 从低到高 限制 //
try {
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);//建立连接
PreparedStatement pstmt = conn.prepareStatement(sql);//创建一个对象
pstmt.setString(1, difficulty);//设置占位符
pstmt.setInt(2, limit);//设置占位符
try (ResultSet rs = pstmt.executeQuery()) {//执行SQL语句
while (rs.next()) {
scores.add(new ScoreRecord(
rs.getString("username"),
rs.getInt("time_used"),
rs.getTimestamp("completion_time")
));
}
}
} catch (SQLException e) {
JOptionPane.showMessageDialog(null, "获取排行榜失败:" , "错误", JOptionPane.ERROR_MESSAGE);
}
return scores;
}
}
// 排行榜记录类
private static class ScoreRecord {
private String username;
private int timeUsed;
private Timestamp completionTime;
public ScoreRecord(String username, int timeUsed, Timestamp completionTime) {
this.username = username;
this.timeUsed = timeUsed;
this.completionTime = completionTime;
}
public String getUsername() {
return username;
}
public int getTimeUsed() {
return timeUsed;
}
public Timestamp getCompletionTime() {
return completionTime;
}
public String getFormattedTime() {
int minutes = timeUsed / 60;
int seconds = timeUsed % 60;
return String.format("%02d:%02d", minutes, seconds);
}
}
// 排行榜对话框类
private class LeaderboardDialog extends JDialog {
private JTable leaderboardTable;
private JComboBox<String> difficultyComboBox;
private DefaultTableModel tableModel;
private static final int MAX_SCORES = 10;
public LeaderboardDialog(JFrame parent) {
super(parent, "扫雷排行榜", true);
// 创建表格模型
String[] columnNames = {"排名", "用户名", "用时", "完成时间"};
tableModel = new DefaultTableModel(columnNames, 0) {
@Override
public boolean isCellEditable(int row, int column) {
return false; // 使表格不可编辑
}
};
// 创建表格
leaderboardTable = new JTable(tableModel);
leaderboardTable.getColumnModel().getColumn(0).setPreferredWidth(50);
leaderboardTable.getColumnModel().getColumn(1).setPreferredWidth(150);
leaderboardTable.getColumnModel().getColumn(2).setPreferredWidth(100);
leaderboardTable.getColumnModel().getColumn(3).setPreferredWidth(200);
// 创建难度选择下拉框
JPanel filterPanel = new JPanel();
filterPanel.add(new JLabel("难度:"));
difficultyComboBox = new JComboBox<>(new String[]{"简单", "普通", "困难"});
filterPanel.add(difficultyComboBox);
// 添加难度选择事件监听器
difficultyComboBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
loadLeaderboard((String) difficultyComboBox.getSelectedItem());
}
});
// 创建主面板
JPanel mainPanel = new JPanel(new BorderLayout());
mainPanel.add(filterPanel, BorderLayout.NORTH);
mainPanel.add(new JScrollPane(leaderboardTable), BorderLayout.CENTER);
// 创建关闭按钮
JButton closeButton = new JButton("关闭");
closeButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
dispose();
}
});
JPanel buttonPanel = new JPanel();
buttonPanel.add(closeButton);
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
// 设置对话框属性
getContentPane().add(mainPanel);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setSize(500, 400);
setLocationRelativeTo(parent);
// 加载默认难度的排行榜
loadLeaderboard("简单");
}
private void loadLeaderboard(String difficulty) {
// 清空表格
tableModel.setRowCount(0);
// 从数据库加载排行榜数据
List<ScoreRecord> scores = Mysql_Set.GET_Mysql_Top(difficulty, MAX_SCORES);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 填充表格
for (int i = 0; i < scores.size(); i++) {
ScoreRecord score = scores.get(i);
tableModel.addRow(new Object[]{
i + 1,
score.getUsername(),
score.getFormattedTime(),
dateFormat.format(score.getCompletionTime())
});
}
}
public void showLeaderboard(String difficulty) {
difficultyComboBox.setSelectedItem(difficulty);
loadLeaderboard(difficulty);
setVisible(true);
}
}
// 登录对话框类
private class LoginDialog extends JDialog {
private JTextField usernameField;
private JButton loginButton;
private JButton cancelButton;
private boolean loggedIn = false;
private String username = "";
public LoginDialog(JFrame parent) {
super(parent, "登录", true);
// 创建面板
JPanel panel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(5, 5, 5, 5);
// 用户名标签
gbc.gridx = 0;
gbc.gridy = 0;
gbc.anchor = GridBagConstraints.WEST;
panel.add(new JLabel("用户名:"), gbc);
// 用户名输入框
usernameField = new JTextField(20);
gbc.gridx = 1;
gbc.gridy = 0;
gbc.fill = GridBagConstraints.HORIZONTAL;
panel.add(usernameField, gbc);
// 按钮面板
JPanel buttonPanel = new JPanel();
loginButton = new JButton("登录");
cancelButton = new JButton("取消");
buttonPanel.add(loginButton);
buttonPanel.add(cancelButton);
gbc.gridx = 0;
gbc.gridy = 1;
gbc.gridwidth = 2;
gbc.anchor = GridBagConstraints.CENTER;
panel.add(buttonPanel, gbc);
// 添加事件监听器
loginButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (usernameField.getText().trim().isEmpty()) {
JOptionPane.showMessageDialog(LoginDialog.this,
"请输入用户名", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
username = usernameField.getText().trim();
loggedIn = true;
dispose();
}
});
cancelButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
dispose();
}
});
// 按下Enter键时登录
usernameField.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
loginButton.doClick();
}
}
});
// 设置对话框属性
getContentPane().add(panel);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
pack();
setLocationRelativeTo(parent);
setResizable(false);
}
public boolean isLoggedIn() {
return loggedIn;
}
public String getUsername() {
return username;
}
}
public Sao_lei() {
// 初始化数据库
Mysql_Set.INIT_Mysql();
// 显示登录对话框
LoginDialog loginDialog = new LoginDialog(this);
loginDialog.setVisible(true);
if (loginDialog.isLoggedIn()) {
currentUsername = loginDialog.getUsername();
} else {
// 如果用户取消登录,使用默认用户名
currentUsername = "玩家";
}
// 创建排行榜对话框
leaderboardDialog = new LeaderboardDialog(this);
//=====================================大窗口
JFrame boxmax = new JFrame("扫雷 - " + currentUsername);
boxmax.setSize(500, 600);
boxmax.setLayout(new BorderLayout()); // 布局为 BorderLayout
boxmax.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 设置关闭操作
boxmax.setVisible(true); // 窗口可见
//====================================大窗口结束
//====================================顶部I
JMenuBar topmenu = new JMenuBar();
JMenu menu1 = new JMenu("开始游戏");
topmenu.add(menu1);
JMenu menu2 = new JMenu("难度选择");
topmenu.add(menu2);
JMenu menu3 = new JMenu("排行榜");
topmenu.add(menu3);
menu1.setFont(new Font("宋体", Font.PLAIN, 17));
menu2.setFont(new Font("宋体", Font.PLAIN, 17));
menu3.setFont(new Font("宋体", Font.PLAIN, 17));
mo_shi_1 = new JMenuItem("简单");
menu2.add(mo_shi_1);
mo_shi_2 = new JMenuItem("普通");
menu2.add(mo_shi_2);
mo_shi_3 = new JMenuItem("困难");
menu2.add(mo_shi_3);
viewLeaderboard = new JMenuItem("查看排行榜");
menu3.add(viewLeaderboard);
boxmax.setJMenuBar(topmenu);
//=====================================顶部I
//===========================================================顶部II
JPanel box_tops = new JPanel();
box_tops.setLayout(new BorderLayout());
label_nadu = new JLabel("难度:未选择(选择难度后开始游戏)");
box_tops.add(label_nadu, BorderLayout.WEST);
face = new ImageIcon("src/face.png");
face.setImage(face.getImage().getScaledInstance(50, 50, Image.SCALE_SMOOTH)); // 缩小笑脸按钮
label_face = new JButton(face);
label_face.setBounds(0, 0, 50, 50); // 设置按钮的大小与图片一致
label_face.setBorderPainted(false); // 不绘制边框
label_face.setContentAreaFilled(false); // 不填充按钮区域
label_face.setFocusPainted(false); // 不绘制焦点边框
box_tops.add(label_face, BorderLayout.CENTER);
label_timer = new JLabel("所用时间00:00");
box_tops.add(label_timer, BorderLayout.EAST);
boxmax.add(box_tops, BorderLayout.NORTH);
//===========================================================顶部II
//===========================================================中心区域
box_center = new JPanel();
JLabel game_text = new JLabel("<html>《基于Java的扫雷》<br><br><br>" +
"1. 先选择游戏难度<br>" +
"2. 点击笑脸重置游戏<br>" +
"3. 右键标记<br><br>" +
"制作人员:<br>" +
"🤵李&nbsp;钊231040600226<br>" +
"🤵梁诗超231040600227<br>" +
"👰刘芮臣231040600229<br></html>"); // 创建标签
game_text.setFont(new Font("宋体", Font.PLAIN, 25)); // 设置字体
game_text.setHorizontalAlignment(JLabel.CENTER); // 设置居中
box_center.add(game_text); // 添加到面板
boxmax.add(box_center, BorderLayout.CENTER);
//============================================================中心区域结束
//===========================================================监听
mo_shi_1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
rows = 10;
cols = 10;
lei = 10;
mo_shi_main(rows, cols, lei);
label_nadu.setText("难度:简单");
}
});
mo_shi_2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
rows = 16;
cols = 16;
lei = 40;
mo_shi_main(rows, cols, lei);
label_nadu.setText("难度:普通");
}
});
mo_shi_3.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
rows = 20;
cols = 20;
lei = 80;
mo_shi_main(rows, cols, lei);
label_nadu.setText("难度:困难");
}
});
label_face.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// 检查是否已选择难度
if (!checkDifficultySelected()) {
return;
}
timer.stop(); // 停止计时器
timeall = 0; // 重置计时器
Times(); // 更新计时器显示
mo_shi_main(rows, cols, lei); // 重新初始化游戏
face = new ImageIcon("src/face.png");
face.setImage(face.getImage().getScaledInstance(50, 50, Image.SCALE_SMOOTH)); // 缩小笑脸按钮
label_face.setIcon(face);
label_face.repaint(); // 重新绘制面板
}
});
// 排行榜菜单项监听器
viewLeaderboard.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// 显示排行榜对话框
if (label_nadu.getText().contains("未选择")) {
leaderboardDialog.showLeaderboard("简单");
} else {
String difficulty = label_nadu.getText().substring(3);
leaderboardDialog.showLeaderboard(difficulty);
}
}
});
//=============================================================监听结束
//====================================================================计时器
timer = new Timer(1000, new ActionListener() {
public void actionPerformed(ActionEvent e) {
timeall++;
Times();
}
});
//====================================================================计时器结束
}
//==========================================================================模式函数
private void mo_shi_main(int rows, int cols, int lei) {
box_center.removeAll(); // 清除原有组件
box_center.setLayout(new GridLayout(rows, cols)); // 设置新的布局
box_x = new JButton[rows * cols]; // 创建按钮数组
mineMap = generateMinesweeperMap(rows, cols, lei); // 生成地图
flagsPlaced = 0; // 重置标记数量
for (int i = 0; i < rows * cols; i++) {
box_x[i] = new JButton("");
final int index = i; // 用于匿名类中访问
box_x[i].addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
revealCell(index / cols, index % cols); // 点击时揭示格子
}
});
box_x[i].addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON3) { // 右键标记
markCell(index / cols, index % cols);
}
}
});
box_center.add(box_x[i]); // 添加到面板
}
box_center.revalidate(); // 重新验证布局
box_center.repaint(); // 重新绘制面板
timeall = 0; // 重置计时器
Times(); // 更新计时器显示
timer.start(); // 启动计时器
}
//==========================================================================模式函数结束
//=========================================================================================生成扫雷地图
private int[][] generateMinesweeperMap(int rows, int cols, int lei) {
int[][] map = new int[rows][cols];
Random random = new Random();
//1.定义二维数组,定义随机数生成器
//2.遍历总的雷的数量
//3.使用random.nextInt()方法随机生成0-rows-1的整数传给r作为行坐标
//4.使用random.nextInt()方法随机生成0-cols-1的整数穿给c作为列坐标
//5.判断二维数组中对应坐标的值是否为-1如果不是-1将其赋值为-1并将lei_num加1
int lei_num = 0;
while (lei_num < lei) {
int r = random.nextInt(rows);//随机生成0-rows-1的整数
int c = random.nextInt(cols);
if (map[r][c] != -1) {
map[r][c] = -1;
lei_num++;
}
}
//1.遍历总的行和列
//2.判断到此处不为雷
//3.定义一个变量lei_num_count记录周围雷的数量
//4.设置for循环-1到1的偏离值通过行列加减遍历周围8个格子
//5.跳过自身
//6.新的偏离值,固定在全局中,
//7.判断周围是否为雷如果是lei_num_count加1
//8.将lei_num_count赋值给二维数组对应坐标的值
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
if (map[r][c] != -1) {
int lei_num_count = 0;//记录周围雷的数量
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
if (i == 0 && j == 0) continue; // 跳过自身
int newRow = r + i;
int newCol = c + j;
if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols && map[newRow][newCol] == -1) {
lei_num_count++;
}
}
}
map[r][c] = lei_num_count;
}
}
}
return map;
}
//========================================================================================生成扫雷地图结束
// 揭示格子
private void revealCell(int row, int col) {
if (row < 0 || row >= rows || col < 0 || col >= cols) return; // 超出范围
if (!box_x[row * cols + col].getText().isEmpty()) return; // 避免重复点击
if (mineMap[row][col] == -1) {
box_x[row * cols + col].setText("💥"); // 地雷
box_x[row * cols + col].setBackground(Color.RED);
timer.stop(); // 停止计时器
showGameOverDialog(); // 显示游戏结束对话框
} else {
box_x[row * cols + col].setText(String.valueOf(mineMap[row][col]));
box_x[row * cols + col].setEnabled(false);
if (mineMap[row][col] == 0) {
// 如果周围没有地雷,递归揭示周围格子
revealCell(row - 1, col);
revealCell(row + 1, col);
revealCell(row, col - 1);
revealCell(row, col + 1);
}
}
checkWinCondition(); // 检查是否胜利
}
// 标记格子
private void markCell(int row, int col) {
if (!box_x[row * cols + col].isEnabled()) return; // 已揭示的格子不能标记
if (box_x[row * cols + col].getText().isEmpty()) {
box_x[row * cols + col].setText("🚩"); // 放置标记
flagsPlaced++;
} else if (box_x[row * cols + col].getText().equals("🚩")) {
box_x[row * cols + col].setText(""); // 移除标记
flagsPlaced--;
}
checkWinCondition(); // 检查是否胜利
}
// 检查胜利条件
private void checkWinCondition() {
int revealedCells = 0; // 已揭示的非雷格子数量
int flaggedMines = 0; // 已标记的雷格子数量
for (int i = 0; i < rows * cols; i++) {
int row = i / cols;
int col = i % cols;
if (!box_x[i].isEnabled()) { // 格子已被揭示
if (mineMap[row][col] != -1) { // 非雷格子
revealedCells++;
}
} else if (box_x[i].getText().equals("🚩")) { // 格子已被标记
if (mineMap[row][col] == -1) { // 标记的格子是雷
flaggedMines++;
}
}
}
// 判断胜利条件
if (revealedCells == (rows * cols - lei) || flaggedMines == lei) {
timer.stop(); // 停止计时器
// 获取当前难度
String difficulty = label_nadu.getText().substring(3);
// 保存成绩到数据库
Mysql_Set.ADD_Mysql(currentUsername, timeall, difficulty);
// 显示胜利消息
JOptionPane.showMessageDialog(this,
"恭喜,你赢了!\n" +
"用户名:" + currentUsername + "\n" +
"难度:" + difficulty + "\n" +
"时间:" + label_timer.getText(),
"游戏胜利", JOptionPane.INFORMATION_MESSAGE);
// 显示排行榜
leaderboardDialog.showLeaderboard(difficulty);
}
}
// 显示游戏结束对话框
private void showGameOverDialog() {
JOptionPane.showMessageDialog(this,
"游戏结束!\n" +
"用户名:" + currentUsername + "\n" +
"难度:" + label_nadu.getText().substring(3) + "\n" +
"时间:" + label_timer.getText(),
"游戏结束", JOptionPane.INFORMATION_MESSAGE);
// 笑脸按钮换为苦脸
face = new ImageIcon("src/noface.png");
face.setImage(face.getImage().getScaledInstance(50, 50, Image.SCALE_SMOOTH)); // 缩小笑脸按钮
label_face.setIcon(face);
label_face.repaint(); // 重新绘制面板
}
// 检查是否已选择难度
private boolean checkDifficultySelected() {
if (label_nadu.getText().contains("未选择")) {
JOptionPane.showMessageDialog(this,
"请先选择游戏难度!",
"提示", JOptionPane.WARNING_MESSAGE);
return false;
}
return true;
}
// 更新计时器标签
private void Times() {
int minutes = timeall / 60;
int seconds = timeall % 60;
label_timer.setText(String.format("所用时间:%02d:%02d", minutes, seconds));
}
public static void main(String[] args) {
new Sao_lei();
}
}