当前位置:首页学习笔记Java笔记3.7 综合练习,石头迷阵游戏编写

3.7 综合练习,石头迷阵游戏编写

01-窗体对象JFrame(窗体创建)

3.7 综合练习,石头迷阵游戏编写
3.7 综合练习,石头迷阵游戏编写

02-组件

3.7 综合练习,石头迷阵游戏编写

按钮组件创建(JButton)

3.7 综合练习,石头迷阵游戏编写

注意事项

注意:JFrame的面板,对于组件,默认有一套摆放规则,如果希望按照自己的意思进行摆放,需要做一个设置

// 取消默认布局
frame.setLayout(null);
// 添加一个按钮
JButton btn = new JButton("点我啊~");
btn.setBounds(50, 50, 100, 100);
// 获取面板对象,并添加按钮
frame.getContentPane().add(btn);

完善前面代码演示

3.7 综合练习,石头迷阵游戏编写

JLabel组件

JLabel:窗体中的一个区域(空间),在这个空间里面摆放图片和文字

  • JLabel(String text)使用指定的文本创建一个JLabel对象
  • JLabel(Icon image)创建一个具有指定图像的JLabel对象

代码演示

3.7 综合练习,石头迷阵游戏编写

JLabel的注意事项

  1. 展示图片的时候,要注意图片的大小
  2. 如果多个组件重叠,后添加的组件,是从底部塞进去的

事件

事件是可以被组件识别的操作,当你对组件干了某件操作之后,就会执行对应的代码

3.7 综合练习,石头迷阵游戏编写

ActionListener:动作监听(鼠标点击,空格按下了)

3.7 综合练习,石头迷阵游戏编写

MouseListener:鼠标监听(鼠标滑入滑出,按下松开,点击)

3.7 综合练习,石头迷阵游戏编写

适配器设计模式

情况:我需要用一个接口,但是只需要接口中的一部分方法
----------------------------------------------------------------------------
解决方案:使用适配器设计模式
		 编写一个适配器类(中间类),实现接口并重写所有抽象方法
		 这里的抽象方法都是空实现,还要将适配器类改为抽象类,避免被别人创建对象使用
		 再让具体的类,继承适配器类,重写自己需要的方法即可

可以用功能自带的Adapter类,来达到一个适配器的效果
    例如:
    //鼠标
    frame.addMouseListener(new MouseAdapter(){
	}
    //键盘                       
    frame.addKeyListener(new KeyAdapter(){
    }

KeyListener:键盘监听(此代码演示用的适配器设计模式改进)

3.7 综合练习,石头迷阵游戏编写

石头迷阵游戏编写

游戏界面编写

3.7 综合练习,石头迷阵游戏编写
import javax.swing.*;

public class Test {
    public static void main(String[] args) {
        // 创建窗体对象
        JFrame frame = new JFrame("石头迷阵单机版v1.0");
        //设置窗体大小
        frame.setSize(514, 595);
        // 设置窗体居中
        frame.setLocationRelativeTo(null);
        // 设置窗体置顶
        frame.setAlwaysOnTop(true);
        // 取消默认布局
        frame.setLayout(null);
        // 设置窗体的关闭模式
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // 定义一个统计变量定义初始值,让for循环来完成多个图片的加载
        int count = 0;

        // 添加石头方块
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                JLabel imageLabel = new JLabel(new ImageIcon("F:\\JavaCodes\\Pre class\\day04-code\\src\\com\\itheima\\stonepuzzle\\image\\" + count + ".png"));
                imageLabel.setBounds(50 + 100 * j, 90 + 100 * i, 100, 100);
                frame.getContentPane().add(imageLabel);
                count++;
            }
        }

        // 添加背景图片
        JLabel background = new JLabel(new ImageIcon("F:\\JavaCodes\\Pre class\\day04-code\\src\\com\\itheima\\stonepuzzle\\image\\background.png"));
        background.setBounds(26, 30, 450, 484);
        frame.getContentPane().add(background);

        //设置窗体可见:需要放在最后调用
        frame.setVisible(true);
    }
}

根据二维数组优化代码

我们要想石头移动,应该把它们放在一个数组当中,以后在加载图片的时候就可以根据二维数组来加载图片

import javax.swing.*;

public class Test {
    public static void main(String[] args) {

        int[][] data = {
                {1, 2, 3, 4},
                {5, 6, 7, 8},
                {9, 10, 11, 12},
                {13, 14, 15, 0}
        };

        // 创建窗体对象
        JFrame frame = new JFrame("石头迷阵单机版v1.0");
        //设置窗体大小
        frame.setSize(514, 595);
        // 设置窗体居中
        frame.setLocationRelativeTo(null);
        // 设置窗体置顶
        frame.setAlwaysOnTop(true);
        // 取消默认布局
        frame.setLayout(null);
        // 设置窗体的关闭模式
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // 添加石头方块
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                JLabel imageLabel = new JLabel(new ImageIcon("F:\\JavaCodes\\Pre class\\day04-code\\src\\com\\itheima\\stonepuzzle\\image\\" + data[i][j] + ".png"));
                imageLabel.setBounds(50 + 100 * j, 90 + 100 * i, 100, 100);
                frame.getContentPane().add(imageLabel);
            }
        }

        // 添加背景图片
        JLabel background = new JLabel(new ImageIcon("F:\\JavaCodes\\Pre class\\day04-code\\src\\com\\itheima\\stonepuzzle\\image\\background.png"));
        background.setBounds(26, 30, 450, 484);
        frame.getContentPane().add(background);

        //设置窗体可见:需要放在最后调用
        frame.setVisible(true);
    }

根据继承改进优化代码

整体步骤

1.定义MainFrame类继承JFrame

2.将代码抽取到一个单独的方法initFrame()

3.将绘制界面的代码,抽取为一个单独的方法paintView()

4.将二维数组提取到成员变量的位置

5,对JFrame类中方法的调用方式,更换为super,或直接省略

6.在构造方法中,调用initFrame()和paintView()

7.在构造方法的最后调用setVisible(true);

Test测试代码

public class Test {
    public static void main(String[] args) {
        new MainFrame();
    }
}

MainFrame子类继承父类改进代码

因为JFrame不允许添加功能,所以我们应该自己定义一个子类来继承父类,用子类来丰富我们的代码

因为子类继承了父类,那我们就不需要使用JFrame frame = new JFrame的方式来调用代码,也不用frame.的方式来进行调用父类方法

例如:

frame.setSize(514, 595);
可以改进为:
setSize(514, 595);
import javax.swing.*;

/**
 * 继承JFrame,让自己的类,也变成窗体类,方便丰富新的功能
 */
public class MainFrame extends JFrame {

    int[][] data = {
            {1, 2, 3, 4},
            {5, 6, 7, 8},
            {9, 10, 11, 12},
            {13, 14, 15, 0}
    };

    public MainFrame() {
        initFrame();
        paintView();
        //设置窗体可见
        setVisible(true);
    }

    /**
     * 此方法用于构建窗体
     */
    public void initFrame() {
        // 设置窗体标题
        setTitle("石头迷阵单机版v1.0");
        //设置窗体大小
        setSize(514, 595);
        // 设置窗体居中
        setLocationRelativeTo(null);
        // 设置窗体置顶
        setAlwaysOnTop(true);
        // 取消默认布局
        setLayout(null);
        // 设置窗体的关闭模式
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    /**
     * 此方法绘制游戏界面
     */
    public void paintView() {
        // 添加石头方块
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                JLabel imageLabel = new JLabel(new ImageIcon("F:\\JavaCodes\\Pre class\\day04-code\\src\\com\\itheima\\stonepuzzle\\image\\" + data[i][j] + ".png"));
                imageLabel.setBounds(50 + 100 * j, 90 + 100 * i, 100, 100);
                getContentPane().add(imageLabel);
            }
        }

        // 添加背景图片
        JLabel background = new JLabel(new ImageIcon("F:\\JavaCodes\\Pre class\\day04-code\\src\\com\\itheima\\stonepuzzle\\image\\background.png"));
        background.setBounds(26, 30, 450, 484);
        getContentPane().add(background);
    }
}

石头方块打乱

核心思路

遍历二维数组获取每一个元素,和其他元素随机交换

可以用随机数来进行操作

int randomX = r.nextInt(4);

int randomY = r.nextInt(4);

实现步骤

1.遍历二维数组,获取到每一个元素

2.在遍历的过程中,产生两个随机的索引

3.让当前元素,和随机索引所指向的元素进行交换

代码实现

 /**
     * 此方法用于初始化二维数组的数据,打乱二维数组
     */
    public void initData() {

        Random r = new Random();

        for (int i = 0; i < data.length; i++) {
            for (int j = 0; j < data[i].length; j++) {
                // 在遍历的过程中,产生两个随机的索引
                int randomX = r.nextInt(4);
                int randomY = r.nextInt(4);
                //让当前元素,和随机索引所指向的元素进行交换
                //当前元素data[i][j]
                //随机索引指向的元素 data[randomX][randomY]
                int temp = data[i][j];
                data[i][j] = data[randomX][randomY];
                data[randomX][randomY] = temp;
            }
        }
    }

石头移动业务

注册监听事件

既然子类都继承了父类方法,那我们也可以直接用子类调用父类中的KeyListener接口

public class MainFrame extends JFrame implements KeyListener {
// 为窗体注册键盘监听
// 思路:需要对窗体对象注册监听,而我自己的这个类就是窗体类,this代表当前类对象
this.addKeyListener(this);

重写父类中的KeyListener接口的keyPressed方法

@Override
    public void keyPressed(KeyEvent e) {
        int keyCode = e.getKeyCode();
        if (keyCode == 37) {
            System.out.println("左");
        } else if (keyCode == 38) {
            System.out.println("上");
        } else if (keyCode == 39) {
            System.out.println("右");
        } else if (keyCode == 40) {
            System.out.println("下");
        }
    }

// 以下代码不用管
@Override
public void keyTyped(KeyEvent e) {

}

@Override
public void keyReleased(KeyEvent e) {

}

获取0号元素的地址

在public class MainFrame extends JFrame implements KeyListener {下定义两个成员变量
    int row;	//行
    int column;	//列
for (int i = 0; i < data.length; i++) {
    for (int j = 0; j < data[i].length; j++) {
        if (data[i][j] == 0) {
            // 记录0号元素(空白块的坐标)
            row = i;
            column = j;
        }
    }
}

石头移动代码

在paintView()方法中加入移除面包组件和刷新界面的逻辑

public void paintView() {
    // 移除面板中的组件
    getContentPane().removeAll();

    // 添加石头方块
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 4; j++) {
            JLabel imageLabel = new JLabel(new ImageIcon("F:\\JavaCodes\\Pre class\\day04-code\\src\\com\\itheima\\stonepuzzle\\image\\" + data[i][j] + ".png"));
            imageLabel.setBounds(50 + 100 * j, 90 + 100 * i, 100, 100);
            getContentPane().add(imageLabel);
        }
    }

    // 添加背景图片
    JLabel background = new JLabel(new ImageIcon("F:\\JavaCodes\\Pre class\\day04-code\\src\\com\\itheima\\stonepuzzle\\image\\background.png"));
    background.setBounds(26, 30, 450, 484);
    getContentPane().add(background);

    // 刷新界面
    getContentPane().repaint();
}

在注册监听的方法里接入paintView()方法

public void keyPressed(KeyEvent e) {
    int keyCode = e.getKeyCode();
    move(keyCode);
    // 每一次移动完,重新绘制界面
    paintView();
}

我们从public void keyPressed(KeyEvent e) 方法中把实现石头移动的代码单独抽取出来,避免public void keyPressed(KeyEvent e) 方法代码太臃肿

/**
 * 此方法用于处理移动业务
 *
 * @param keyCode
 */
private void move(int keyCode) {
    if (keyCode == 37) {
        // (空白块)和(右侧的数据)交换
        // data[row][column]  data[row][column + 1]
        int temp = data[row][column];
        data[row][column] = data[row][column + 1];
        data[row][column + 1] = temp;
        // 修改空白坐标
        column++;
    } else if (keyCode == 38) {
        // (空白块)和(下方的数据)交换
        // data[row][column]  data[row + 1][column]
        int temp = data[row][column];
        data[row][column] = data[row + 1][column];
        data[row + 1][column] = temp;
        // 修改空白坐标
        row++;
    } else if (keyCode == 39) {
        // (空白块)和(左侧的数据)交换
        // data[row][column]  data[row][column - 1]
        int temp = data[row][column];
        data[row][column] = data[row][column - 1];
        data[row][column - 1] = temp;
        // 修改空白坐标
        column--;
    } else if (keyCode == 40) {
        // (空白块)和(上方的数据)交换
        // data[row][column]  data[row - 1][column]
        int temp = data[row][column];
        data[row][column] = data[row - 1][column];
        data[row - 1][column] = temp;
        // 修改空白坐标
        row--;
    }
}

Bug分析和解决

在上面代码的时候,移动一次后,在向相同的方向移动的话,会出现索引越界的报错(用下方图片来if判断即可,如果超了下方图片的值返回return;)

3.7 综合练习,石头迷阵游戏编写

因为这个游戏的算法很基础,可能会导致无法完成游戏,我们可以做一个作弊器来完成游戏(是不是很骚)

// 这里设置的90是键盘上z的地址,当我们按下z的时候,直接重新生成数组结束游戏
else if (keyCode == 90) {
    data = new int[][] {
            {1, 2, 3, 4},
            {5, 6, 7, 8},
            {9, 10, 11, 12},
            {13, 14, 15, 0}
    };
}

判定游戏胜利

3.7 综合练习,石头迷阵游戏编写
/**
 * 此方法用于校验游戏是否胜利
 */
public boolean victory() {
    for (int i = 0; i < data.length; i++) {
        for (int j = 0; j < data[i].length; j++) {
            if (data[i][j] != win[i][j]) {
                return false;
            }
        }
    }
    return true;
}

在public void paintView() { 方法中的移除面板中的组件后添加,假如在移除面板中的组件的前面添加的话,它又会移除面板组件

// 统计游戏步数
JLabel scoreLabel = new JLabel("步数:" + count);
scoreLabel.setBounds(50, 20, 100, 20);
getContentPane().add(scoreLabel);

// 添加游戏胜利图片
if (victory()) {
    JLabel winJLabel = new JLabel(new ImageIcon("F:\\JavaCodes\\Pre class\\day04-code\\src\\com\\itheima\\stonepuzzle\\image\\win.png"));
    winJLabel.setBounds(124, 230, 266, 88);
    getContentPane().add(winJLabel);
}

统计步数

  1. 在子类中定义一个count成员变量用于统计步数
  2. 在每个移动方块逻辑下添加count++统计移动的步数(作弊器不添加)
private void move(int keyCode) {

    if (victory()) {
        return;
    }

    if (keyCode == 37) {

        if (column == 3) {
            return;
        }
        // (空白块)和(右侧的数据)交换
        // data[row][column]  data[row][column + 1]
        int temp = data[row][column];
        data[row][column] = data[row][column + 1];
        data[row][column + 1] = temp;
        // 修改空白坐标
        column++;
        count++;
    } else if (keyCode == 38) {

        if (row == 3) {
            return;
        }
        // (空白块)和(下方的数据)交换
        // data[row][column]  data[row + 1][column]
        int temp = data[row][column];
        data[row][column] = data[row + 1][column];
        data[row + 1][column] = temp;
        // 修改空白坐标
        row++;
        count++;
    } else if (keyCode == 39) {

        if (column == 0) {
            return;
        }
        // (空白块)和(左侧的数据)交换
        // data[row][column]  data[row][column - 1]
        int temp = data[row][column];
        data[row][column] = data[row][column - 1];
        data[row][column - 1] = temp;
        // 修改空白坐标
        column--;
        count++;
    } else if (keyCode == 40) {

        if (row == 0) {
            return;
        }
        // (空白块)和(上方的数据)交换
        // data[row][column]  data[row - 1][column]
        int temp = data[row][column];
        data[row][column] = data[row - 1][column];
        data[row - 1][column] = temp;
        // 修改空白坐标
        row--;
        count++;
    } else if (keyCode == 90) {
        data = new int[][]{
                {1, 2, 3, 4},
                {5, 6, 7, 8},
                {9, 10, 11, 12},
                {13, 14, 15, 0}
        };
    }
}

重新开始游戏

重新开始游戏也是属于绘制游戏界面的方法,所以我们在绘制游戏界面的方法里添加,注意:也是添加在移除面板中的组件后面

// 重新游戏
JButton btn = new JButton("重新游戏");
btn.setBounds(350, 20, 100 ,20);
btn.setFocusable(false);
btn.addActionListener(this);
getContentPane().add(btn);

我们知道接口可以多实现,所以,我们在子类的后面,号分割后,添加ActionListener,选中后,快捷键(Alt + Enter)进行重构

public class MainFrame extends JFrame implements KeyListener, ActionListener {

重构代码

/**
 * 此方法用于重新游戏的逻辑
 */
@Override
public void actionPerformed(ActionEvent e) {
    count = 0;
    initData();
    paintView();   
}

游戏练习编写完成

完整代码

Test测试类代码:

public class Test {
    public static void main(String[] args) {
        new MainFrame();
    }
}

游戏所有逻辑代码

温馨提示:

文章标题:3.7 综合练习,石头迷阵游戏编写

文章链接:https://www.cutrui.cn/3056.html

更新时间:2023年08月02日

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

给TA打赏
共{{data.count}}人
人已打赏
Java笔记学习笔记

3.6 代码块-内部类-Lambda表达式-模板设计模式-toString方法

2023-7-31 13:26:48

Java笔记学习笔记

3.8 常用API-包装类-时间API

2023-8-8 16:28:22

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索

你有新的私信

请务必要查看您的私信哟~~