自学Java-AI结合GUI开发一个石头迷阵的游戏
- 准备环节
- 1、创建石头迷阵的界面
- 2、打乱顺序
- 3、控制上下左右移动
- 4、判断是否通关
- 5、统计移动步骤,重启游戏
- 6、拓展问题
准备环节
技术:
1、GUI界面编程
2、二维数组
3、程序流程控制
4、面向对象编程
∙
\bullet
∙创建一个模块用于开发石头迷阵游戏,模块名称取名为:stone-maze
∙
\bullet
∙导入项目需要的资源包到src目录下:主要是一些图片文件,在image文件夹下
∙
\bullet
∙创建项目包:com.itheima.
1、创建石头迷阵的界面
∙
\bullet
∙定义主界面类,MainFrame继承JFrame
∙
\bullet
∙初始化窗口大小
∙
\bullet
∙初始化界面图片
∙
\bullet
∙初始化界面菜单:系统退出,重启游戏
完成代码如下:
package com.itheima;
import javax.swing.*;
// 自定义窗口类,创建对象,展示一个主窗口。
public class MainFrame extends JFrame {
// 设置图片位置
private static final String imagePath = "stone-maze/src/image/";
// 准备一个数组,用户存储数字色块的行列位置:4行4列
private int[][] imageData = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
{13, 14, 15, 0}
};
public MainFrame() {
// 1、调用一个初始化方法:初始化窗口大小等信息。
initFrame();
// 2、初始化界面:展示数字色块。
initImage();
// 3、初始化系统菜单:点击弹出菜单信息是系统退出,重启游戏。
initMenu();
// 设置窗口可见
this.setVisible(true);
}
private void initMenu() {
JMenuBar menuBar = new JMenuBar(); // 创建一个菜单栏
JMenu menu = new JMenu("系统");
JMenuItem exitJi = new JMenuItem("退出");
menu.add(exitJi); // 添加子菜单
exitJi.addActionListener(e -> { // 添加点击事件
// 退出游戏
dispose();
});
JMenuItem restartJi = new JMenuItem("重启");
menu.add(restartJi);
restartJi.addActionListener(e -> { // 添加点击事件
// 重启游戏
});
menuBar.add(menu); // 添加到菜单栏中
this.setJMenuBar(menuBar);
}
private void initImage() {
// 1、展示一个行列矩阵的图片色块依次铺满窗口(4 * 4)
for (int i = 0; i < imageData.length; i++) { // 遍历行
for (int j = 0; j < imageData[i].length; j++) { // 遍历列
// 拿到图片的名称
String imageName = imageData[i][j] + ".png";
// 2、创建一个JLabel对象,设置图片给他展示。
JLabel label = new JLabel();
// 3、设置图片到label对象中去。
label.setIcon(new ImageIcon(imagePath + imageName));
// 4、设置数字色块的位置
label.setBounds(25 + j * 100, 60 + i * 100, 100, 100);
// 5、把这个图片展示到窗口中
this.add(label);
}
}
// 设置窗口的背景图片
JLabel background = new JLabel(new ImageIcon(imagePath + "background.png"));
background.setBounds(0, 0, 450, 484);
this.add(background);
}
private void initFrame() {
// 设置窗口标题
this.setTitle("石子迷宫 V 1.0");
// 设置窗口大小
this.setSize(465, 575);
// 设置窗口关闭方式
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 设置窗口的居中显示
this.setLocationRelativeTo(null);
// 设置布局方式为绝对位置定位
this.setLayout(null);
}
}
2、打乱顺序
∙ \bullet ∙打乱界面的图片顺序,让游戏具备可玩性:使用方法如下
打乱二维数组中的元素顺序:initRandomArray();
完成代码如下:
private void initRandomArray() {
// 1、打乱数组
for (int i = 0; i < imageData.length; i++) { // 遍历行
for (int j = 0; j < imageData[i].length; j++) { // 遍历列
// 随机两个行列位置,让这两个位置交换。
int i1 = (int)(Math.random() * imageData.length);
int j1 = (int)(Math.random() * imageData.length);
int i2 = (int)(Math.random() * imageData.length);
int j2 = (int)(Math.random() * imageData.length);
int temp = imageData[i1][j1];
imageData[i1][j1] = imageData[i2][j2];
imageData[i2][j2] = temp;
}
}
}
3、控制上下左右移动
∙ \bullet ∙给窗口绑定上下左右按键事件
∙
\bullet
∙控制位置的交换
–定位当前空白色块的位置。
–根据用户点击的方位确定交换哪个数据,到数组中交换。
∙
\bullet
∙重新绘制主界面的内容
–让主界面按照二维数组的最新内容刷新界面
public enum Direction {
UP, DOWN, LEFT, RIGHT;
}
完成代码如下:
package com.itheima;
import javax.swing.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
// 自定义窗口类,创建对象,展示一个主窗口。
public class MainFrame extends JFrame {
// 设置图片位置
private static final String imagePath = "stone-maze/src/image/";
// 准备一个数组,用户存储数字色块的行列位置:4行4列
private int[][] imageData = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
{13, 14, 15, 0}
};
// 定义两个整数变量记录当前空白色块的位置。
private int row; // 当前空白色块的行位置
private int col; // 当前空白色块的列位置
public MainFrame() {
// 1、调用一个初始化方法:初始化窗口大小等信息。
initFrame();
// 4、打乱数组色块的顺序,再提示图片
initRandomArray();
// 2、初始化界面:展示数字色块。
initImage();
// 3、初始化系统菜单:点击弹出菜单信息是系统退出,重启游戏。
initMenu();
// 5、给当前窗口绑定上下左右按键事件。
initKeyPressEvent();
// 设置窗口可见
this.setVisible(true);
}
private void initKeyPressEvent() {
// 给当前窗口绑定上下左右按键事件。
this.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
// 判断当前按键的编号
int keyCode = e.getKeyCode();
// 判断按键编号,上下左右
switch (keyCode) {
case KeyEvent.VK_UP:
switchAndMove(Direction.UP);
// 用户按下上键,把图片上移。
break;
case KeyEvent.VK_DOWN:
switchAndMove(Direction.DOWN);
// 用户按下下键,把图片下移。
break;
case KeyEvent.VK_LEFT:
switchAndMove(Direction.LEFT);
// 用户按下左键,把图片左移。
break;
case KeyEvent.VK_RIGHT:
switchAndMove(Direction.RIGHT);
// 用户按下右键,把图片右移。
break;
}
}
});
}
// 控制数据交换和图片移动
private void switchAndMove(Direction r) {
// 判断图片的方向,再控制图片移动。
switch (r) {
case UP:
// 上交换的条件是行必须 < 3,然后才开始交换。
if (row < imageData.length - 1) {
// 当前空白色块位置:rol col
// 需要被交换的位置:row + 1 col
int temp = imageData[row][col];
imageData[row][col] = imageData[row + 1][col];
imageData[row + 1][col] = temp;
// 更新当前空白色块的位置。
row++;
}
break;
case DOWN:
if (row > 0) {
// 当前空白色块位置:row col
// 需要被交换的位置:row - 1 col
int temp = imageData[row][col];
imageData[row][col] = imageData[row - 1][col];
imageData[row - 1][col] = temp;
// 更新当前空白色块的位置。
row--;
}
break;
case LEFT:
// 左交换的条件是列必须 < 3,然后才开始交换。
if (col < imageData.length - 1) {
// 当前空白色块位置:row col
// 需要被交换的位置:row col + 1
int temp = imageData[row][col];
imageData[row][col] = imageData[row][col + 1];
imageData[row][col + 1] = temp;
// 更新当前空白色块的位置。
col++;
}
break;
case RIGHT:
if (col > 0) {
int temp = imageData[row][col];
imageData[row][col] = imageData[row][col - 1];
imageData[row][col - 1] = temp;
col--;
}
break;
}
// 重新刷新界面!!!
initImage();
}
private void initRandomArray() {
// 1、打乱数组
for (int i = 0; i < imageData.length; i++) { // 遍历行
for (int j = 0; j < imageData[i].length; j++) { // 遍历列
// 随机两个行列位置,让这两个位置交换。
int i1 = (int)(Math.random() * imageData.length);
int j1 = (int)(Math.random() * imageData.length);
int i2 = (int)(Math.random() * imageData.length);
int j2 = (int)(Math.random() * imageData.length);
int temp = imageData[i1][j1];
imageData[i1][j1] = imageData[i2][j2];
imageData[i2][j2] = temp;
}
}
// 定义空白色块的位置。
// 去二维数组中遍历每个数据,只要发现这个数据等于0,这个位置就是当前空白色块的位置。
OUT:
for (int i = 0; i < imageData.length; i++) { // 遍历行
for (int j = 0; j < imageData[i].length; j++) { // 遍历列
if (imageData[i][j] == 0) {
// 定位空白色块的位置
row = i;
col = j;
break OUT; // 跳出两个for循环
}
}
}
}
private void initMenu() {
JMenuBar menuBar = new JMenuBar(); // 创建一个菜单栏
JMenu menu = new JMenu("系统");
JMenuItem exitJi = new JMenuItem("退出");
menu.add(exitJi); // 添加子菜单
exitJi.addActionListener(e -> { // 添加点击事件
// 退出游戏
dispose();
});
JMenuItem restartJi = new JMenuItem("重启");
menu.add(restartJi);
restartJi.addActionListener(e -> { // 添加点击事件
// 重启游戏
});
menuBar.add(menu); // 添加到菜单栏中
this.setJMenuBar(menuBar);
}
private void initImage() {
// 先清空窗口上的全部图层
this.getContentPane().removeAll();
// 1、展示一个行列矩阵的图片色块依次铺满窗口(4 * 4)
for (int i = 0; i < imageData.length; i++) { // 遍历行
for (int j = 0; j < imageData[i].length; j++) { // 遍历列
// 拿到图片的名称
String imageName = imageData[i][j] + ".png";
// 2、创建一个JLabel对象,设置图片给他展示。
JLabel label = new JLabel();
// 3、设置图片到label对象中去。
label.setIcon(new ImageIcon(imagePath + imageName));
// 4、设置数字色块的位置
label.setBounds(20 + j * 100, 60 + i * 100, 100, 100);
// 5、把这个图片展示到窗口中
this.add(label);
}
}
// 设置窗口的背景图片
JLabel background = new JLabel(new ImageIcon(imagePath + "background.png"));
background.setBounds(0, 0, 450, 484);
this.add(background);
// 刷新新图层,重新绘制窗口
this.repaint();
}
private void initFrame() {
// 设置窗口标题
this.setTitle("石子迷宫 V 1.0");
// 设置窗口大小
this.setSize(463, 543);
// 设置窗口关闭方式
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 设置窗口的居中显示
this.setLocationRelativeTo(null);
// 设置布局方式为绝对位置定位
this.setLayout(null);
}
}
4、判断是否通关
∙
\bullet
∙用户每操作一步,需要立即判断是否已经通关,如果通过,需要显示胜利的标记。
完成代码如下:
package com.itheima;
import javax.swing.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
// 自定义窗口类,创建对象,展示一个主窗口。
public class MainFrame extends JFrame {
// 设置图片位置
private static final String imagePath = "stone-maze/src/image/";
// 准备一个数组,用户存储数字色块的行列位置:4行4列
private int[][] imageData = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
{13, 14, 15, 0}
};
// 定义一个二维数组,用于存储最终游戏成功的数据顺序
private int[][] winData = new int[][] {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
{13, 14, 15, 0}
};
// 定义两个整数变量记录当前空白色块的位置。
private int row; // 当前空白色块的行位置
private int col; // 当前空白色块的列位置
public MainFrame() {
// 1、调用一个初始化方法:初始化窗口大小等信息。
initFrame();
// 4、打乱数组色块的顺序,再提示图片
initRandomArray();
// 2、初始化界面:展示数字色块。
initImage();
// 3、初始化系统菜单:点击弹出菜单信息是系统退出,重启游戏。
initMenu();
// 5、给当前窗口绑定上下左右按键事件。
initKeyPressEvent();
// 设置窗口可见
this.setVisible(true);
}
private void initKeyPressEvent() {
// 给当前窗口绑定上下左右按键事件。
this.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
// 判断当前按键的编号
int keyCode = e.getKeyCode();
// 判断按键编号,上下左右
switch (keyCode) {
case KeyEvent.VK_UP:
switchAndMove(Direction.UP);
// 用户按下上键,把图片上移。
break;
case KeyEvent.VK_DOWN:
switchAndMove(Direction.DOWN);
// 用户按下下键,把图片下移。
break;
case KeyEvent.VK_LEFT:
switchAndMove(Direction.LEFT);
// 用户按下左键,把图片左移。
break;
case KeyEvent.VK_RIGHT:
switchAndMove(Direction.RIGHT);
// 用户按下右键,把图片右移。
break;
}
}
});
}
// 控制数据交换和图片移动
private void switchAndMove(Direction r) {
// 判断图片的方向,再控制图片移动。
switch (r) {
case UP:
// 上交换的条件是行必须 < 3,然后才开始交换。
if (row < imageData.length - 1) {
// 当前空白色块位置:rol col
// 需要被交换的位置:row + 1 col
int temp = imageData[row][col];
imageData[row][col] = imageData[row + 1][col];
imageData[row + 1][col] = temp;
// 更新当前空白色块的位置。
row++;
}
break;
case DOWN:
if (row > 0) {
// 当前空白色块位置:row col
// 需要被交换的位置:row - 1 col
int temp = imageData[row][col];
imageData[row][col] = imageData[row - 1][col];
imageData[row - 1][col] = temp;
// 更新当前空白色块的位置。
row--;
}
break;
case LEFT:
// 左交换的条件是列必须 < 3,然后才开始交换。
if (col < imageData.length - 1) {
// 当前空白色块位置:row col
// 需要被交换的位置:row col + 1
int temp = imageData[row][col];
imageData[row][col] = imageData[row][col + 1];
imageData[row][col + 1] = temp;
// 更新当前空白色块的位置。
col++;
}
break;
case RIGHT:
if (col > 0) {
int temp = imageData[row][col];
imageData[row][col] = imageData[row][col - 1];
imageData[row][col - 1] = temp;
col--;
}
break;
}
// 重新刷新界面!!!
initImage();
}
private void initRandomArray() {
// 1、打乱数组
for (int i = 0; i < imageData.length; i++) { // 遍历行
for (int j = 0; j < imageData[i].length; j++) { // 遍历列
// 随机两个行列位置,让这两个位置交换。
int i1 = (int)(Math.random() * imageData.length);
int j1 = (int)(Math.random() * imageData.length);
int i2 = (int)(Math.random() * imageData.length);
int j2 = (int)(Math.random() * imageData.length);
int temp = imageData[i1][j1];
imageData[i1][j1] = imageData[i2][j2];
imageData[i2][j2] = temp;
}
}
// 定义空白色块的位置。
// 去二维数组中遍历每个数据,只要发现这个数据等于0,这个位置就是当前空白色块的位置。
OUT:
for (int i = 0; i < imageData.length; i++) { // 遍历行
for (int j = 0; j < imageData[i].length; j++) { // 遍历列
if (imageData[i][j] == 0) {
// 定位空白色块的位置
row = i;
col = j;
break OUT; // 跳出两个for循环
}
}
}
}
private void initMenu() {
JMenuBar menuBar = new JMenuBar(); // 创建一个菜单栏
JMenu menu = new JMenu("系统");
JMenuItem exitJi = new JMenuItem("退出");
menu.add(exitJi); // 添加子菜单
exitJi.addActionListener(e -> { // 添加点击事件
// 退出游戏
dispose();
});
JMenuItem restartJi = new JMenuItem("重启");
menu.add(restartJi);
restartJi.addActionListener(e -> { // 添加点击事件
// 重启游戏
});
menuBar.add(menu); // 添加到菜单栏中
this.setJMenuBar(menuBar);
}
private void initImage() {
// 先清空窗口上的全部图层
this.getContentPane().removeAll();
// 判断是否赢了
if (isWin()) {
// 展示胜利的图片
JLabel label = new JLabel(new ImageIcon(imagePath + "win.png"));
label.setBounds(124, 230, 266, 88);
this.add(label);
}
// 1、展示一个行列矩阵的图片色块依次铺满窗口(4 * 4)
for (int i = 0; i < imageData.length; i++) { // 遍历行
for (int j = 0; j < imageData[i].length; j++) { // 遍历列
// 拿到图片的名称
String imageName = imageData[i][j] + ".png";
// 2、创建一个JLabel对象,设置图片给他展示。
JLabel label = new JLabel();
// 3、设置图片到label对象中去。
label.setIcon(new ImageIcon(imagePath + imageName));
// 4、设置数字色块的位置
label.setBounds(20 + j * 100, 60 + i * 100, 100, 100);
// 5、把这个图片展示到窗口中
this.add(label);
}
}
// 设置窗口的背景图片
JLabel background = new JLabel(new ImageIcon(imagePath + "background.png"));
background.setBounds(0, 0, 450, 484);
this.add(background);
// 刷新新图层,重新绘制窗口
this.repaint();
}
private boolean isWin() {
// 判断是否赢了
for (int i = 0; i < imageData.length; i++) { // 遍历行
for (int j = 0; j < imageData[i].length; j++) { // 遍历列
// 判断当前遍历到的数据是否和winData中的数据是否一致
if (imageData[i][j] != winData[i][j]) {
// 如果不一致,则返回false
return false;
}
}
}
// 如果一致,则返回true,则显示胜利的图片
return true;
}
private void initFrame() {
// 设置窗口标题
this.setTitle("石子迷宫 V 1.0");
// 设置窗口大小
this.setSize(463, 543);
// 设置窗口关闭方式
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 设置窗口的居中显示
this.setLocationRelativeTo(null);
// 设置布局方式为绝对位置定位
this.setLayout(null);
}
}
5、统计移动步骤,重启游戏
∙
\bullet
∙每成功移动一步,都需要累加一次步数
∙
\bullet
∙定义一个变量用于累加步数,并实时展示到界面上
6、拓展问题
∙
\bullet
∙数字华容道的乱序操作,并不是可以随意打乱的,必须满足一定的算法去打乱顺序,这样才是有解的,才能让玩家恢复到有序。有没有简单的算法???