Java进阶(第四期):(Java实战小游戏)Java中的事件和窗体 && 以及面向对象中的设计模板 && 手把手分步骤教会你完成自己的第一个Java小游戏! && 完整的游戏源代码

Java进阶(第四期)

一、(JFrame)窗体

  • 通过代码实现出图形化界面,下面简单的调用出来。
package com.liujintao.frame;

import javax.swing.*;

public class JFrameTest {
    public static void main(String[] LiuJinTao) {
        // 创建窗体对象
        JFrame frame = new JFrame();


        // 设置窗体大小
        frame.setSize(500, 800);


        // 修改窗体的关闭模式(点击× ,将整个程序终止)
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        // 设置窗体的标题
        frame.setTitle("hello frame");

        // 设置窗体可见(一定要放最后,数据就绪后,在显示)
        frame.setVisible(true);
    }
}

效果如下:
Java进阶(第四期):(Java实战小游戏)Java中的事件和窗体 && 以及面向对象中的设计模板 && 手把手分步骤教会你完成自己的第一个Java小游戏! && 完整的游戏源代码_第1张图片

二、(JButton)在窗体中添加按钮组件

    窗体中添加按钮组件
    ---------------------------------
    JButton构造方法:
        1. public JButton():这是一个空白构造
        2. public JButtonString text):创建一个带文本的按钮
        

    ----------------------------
    注意:如果取消了窗体的默认布局,就需要手动指定组件的摆放位置

3. 然后使用:窗体对象.getContentPane().add(按钮组件对象);将按钮组件放到窗体面板中

package com.liujintao.frame.button;

import javax.swing.*;

public class JButtonTest {
    public static void main (String[] LiuJinTao) {
        // 1. 先有窗体在有组件(组件就是窗体里面的所有元素)
        JFrame frame = new JFrame();
        frame.setSize(500, 500);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        // 2. 创建Button组件(使用构造方法)

        // 取消组件的默认摆放位置
        frame.setLayout(null);
        JButton btn = new JButton("Button");
        // 指定组件在窗体面板中的位置
        btn.setBounds(100, 100, 100, 100);
        // 将按钮添加到窗体中的面板中(位置取决于默认还是,自定义)
        frame.getContentPane().add(btn);


        // 将窗体显示出来
        frame.setVisible(true);
    }
}

  • 注意点:如果取消了默认的摆放位置,那么一定要自定义位置,不然Button组件就蒙了。

Java进阶(第四期):(Java实战小游戏)Java中的事件和窗体 && 以及面向对象中的设计模板 && 手把手分步骤教会你完成自己的第一个Java小游戏! && 完整的游戏源代码_第2张图片

三、(JLabel)窗体中开辟区域

  • 主要作用就是在窗体中,开辟空间,然后存放文本图片啥的。

  • 需要注意的:如果我在一个位置区域同时放多张图像,后添加的会放到下面,也就是被覆盖。

    使用JLabel实现在窗体中开辟空间,展示文本和图片
        JLabel构造方法:
            Jlabel(String text) 使用指定的文本创建一个 JLabel对象
            JLabelIcon image) 创建一个具有指定图像的 JLabel 对象
package com.liujintao.frame.label;

import javax.swing.*;

public class JLabelTest {
    public static void main(String[] LiuJinTao) {
        // 创建窗体
        JFrame frame = new JFrame();
        frame.setSize(500, 500);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setLayout(null);

        // 1. 使用JLabel 对象展示文本
        JLabel jb1 = new JLabel("面向对象面向君,");
        jb1.setBounds(50, 50, 100, 100);
        frame.getContentPane().add(jb1);

        JLabel jb2 = new JLabel("不负代码不负君!");
        jb2.setBounds(150, 50, 100, 100);
        frame.getContentPane().add(jb2);


        // 2. 使用 JLabel 对象展示图像
        // 使用ImageIcon构造器拿到图片,在将图片对象放到JLabel空间中
        JLabel JLabel_image_2png = new JLabel(new ImageIcon("D:\\桌面\\文件夹(临时的)\\image\\10.png"));
        JLabel_image_2png.setBounds(100, 300, 100, 100);
        // 将JLabel放到面板中
        frame.getContentPane().add(JLabel_image_2png);


        JLabel JLabel_image_11png = new JLabel(new ImageIcon("D:\\桌面\\文件夹(临时的)\\image\\11.png"));
        JLabel_image_11png.setBounds(150, 150, 100, 100);
        frame.getContentPane().add(JLabel_image_11png);


        frame.setVisible(true);
    }
}

Java进阶(第四期):(Java实战小游戏)Java中的事件和窗体 && 以及面向对象中的设计模板 && 手把手分步骤教会你完成自己的第一个Java小游戏! && 完整的游戏源代码_第3张图片

图像的展示效果

Java进阶(第四期):(Java实战小游戏)Java中的事件和窗体 && 以及面向对象中的设计模板 && 手把手分步骤教会你完成自己的第一个Java小游戏! && 完整的游戏源代码_第4张图片

四、动作监听(ActionListener)

  • 鼠标点击 +空格触发事件

事件源.addActionListener(new ActionListener() {})

package com.liujintao.frame.listener;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class ActionListenerTest {
    public static void main(String [] LiuJinTao) {
        JFrame frame = new JFrame();
        frame.setSize(500, 500);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setLayout(null);

        // 创建事件源
        JButton btn = new JButton("我是事件源");
        btn.setSize(100, 100);
        btn.setBounds(100, 100, 100, 100);
        frame.getContentPane().add(btn);
        btn.addActionListener(new ActionListener () {
            // 重写接口中的抽象内部类
            @Override
            public void actionPerformed(ActionEvent e) {

                System.out.println("事件被触发了");
            }
        });


        frame.setVisible(true);
    }
}


五、键盘监听keyListener

package com.liujintao.frame.listener;

import javax.swing.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

public class KeyListenerTest {
    public static void main(String[] args) {
        JFrame frame = new JFrame();
        frame.setLayout(null);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setSize(500, 500);
        
        frame.addKeyListener(new KeyListener() {
            @Override
            public void keyTyped(KeyEvent e) {
                // 该方法只能监听部分按键
            }
            
            @Override
            public void keyPressed(KeyEvent e) {
                // 键盘按下触发
                
                // 通过事件对象 e上的getKeyCode方法查看键盘表示的数值
                int num = e.getKeyCode();
                if (num == 37) {
                    System.out.println("您按下了左键");
                } else if (num == 38) {
                    System.out.println("您按下了上键");
                } else if (num == 39) {
                    System.out.println("您按下了右键");
                } else if (num == 40) {
                    System.out.println("您按下了下键");
                }
            }
            
            @Override
            public void keyReleased(KeyEvent e) {
                // 键盘弹起触发
            }
        });
        
        
        frame.setVisible(true);
    }
}

六、事件冲突及解决方法

  • 事件冲突:就是当我们同时注册了动作监听和键盘监听,此时默认的是触发焦点点击事件。

  • 注意:按钮组件比较特殊,再创建好之后,程序的焦点,默认就停留在了按钮组件上面, —— 但是按钮组件,其实不需要占用程序的焦点。

  • 解决方案:给我们的按钮取消焦点就好了
    · 事件源.setFocusable(false)

package com.liujintao.frame.listener;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

public class Tips {
    /*
        事件冲突:就是当我们同时注册了动作监听和键盘监听,此时默认的是触发焦点点击事件。
        
        解决方案:给我们的按钮取消焦点就好了
            事件源.setFocusable(false)
     */
    
    public static void main(String[] args) {
        JFrame frame = new JFrame();
        frame.setSize(500, 500);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setLayout(null);
        
        // 创建一个按钮
        JButton btn = new JButton("看我这");
        btn.setBounds(100, 100, 100, 100);
        frame.getContentPane().add(btn);
        
        // 给按钮注册动作事件
        btn.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                System.out.println("我被点击了!");
            }
            
            

        });
        
        // 解决方法,取消按钮的焦点
        btn.setFocusable(false);
        
        // 注册一个键盘按下事件
        frame.addKeyListener(new KeyListener() {
            
            @Override
            public void keyTyped(KeyEvent e) {
            
            }
            
            @Override
            public void keyPressed(KeyEvent e) {
                System.out.println("被按下了!");
            }

            
            @Override
            public void keyReleased(KeyEvent e) {
            
            }
        });
        
        
        frame.setVisible(true);
    }
}

Java进阶(第四期):(Java实战小游戏)Java中的事件和窗体 && 以及面向对象中的设计模板 && 手把手分步骤教会你完成自己的第一个Java小游戏! && 完整的游戏源代码_第5张图片

七、 适配器设计模式

在这里插入图片描述

  • 适配器设计模式:解决接口与接口实现类之间的矛盾问题

  • 实现步骤:

  1. 编写一个xxxAdapter类,实现对应的接口(也就是实现类)
  2. 重写内部的所有抽象方法,但是方法都是空实现,空逻辑的。
  3. 让自己的类去继承适配器类,重写自己需要的方法即可
  4. 为了避免其他类创建适配器类的对象,使用 abstract 进行修饰。方式被创建对象
  • 说白了就是一个中间类,一个类实现了一个接口,但是里面重写的方法都是没有逻辑的。在使用创建一个类去继承这个 实现类(也就是中间类适配器)。这样谁继承了这个实现类,谁就可以调用里面的方法进行自定义了。

八、模板适配器模式

Java进阶(第四期):(Java实战小游戏)Java中的事件和窗体 && 以及面向对象中的设计模板 && 手把手分步骤教会你完成自己的第一个Java小游戏! && 完整的游戏源代码_第6张图片

九、游戏业务开始

9.1 游戏思路分析:

Java进阶(第四期):(Java实战小游戏)Java中的事件和窗体 && 以及面向对象中的设计模板 && 手把手分步骤教会你完成自己的第一个Java小游戏! && 完整的游戏源代码_第7张图片

9.2 基本的模板渲染
package com.liujintao.stonepuzzle;

import javax.swing.*;

public class Test {
    public static void main(String[] args) {
        // 创建资源图片的二维数组
        int[][] arr = {
                {1, 2, 3, 4},
                {5, 6, 7, 8},
                {9, 10, 11, 12},
                {13, 14, 15, 0},
        };
        // 设置窗体
        JFrame frame = new JFrame();
        // 设置窗体大小
        frame.setSize(514, 595);
        // 设置窗体关闭模式
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        // 设置窗体标题
        frame.setTitle("华容道单机版V1.0");
        // 窗口处于对上层
        frame.setAlwaysOnTop(true);
        // 窗口居中显示
        frame.setLocationRelativeTo(null);
        // 取消默布局
        frame.setLayout(null);
        
        // 设置 JLabel 组件 将图片资源放进去
        // i 执行一次,j 执行四次,我相信都知道。只是需要注意的是。那个在一次外循环的时候,不需要变化的,我们用i,需要变化的使用j
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                JLabel imagesJlb = new JLabel(new ImageIcon("D:\\java_code\\Advanced-Codes\\day04-code\\image\\" + arr[i][j] + ".png"));
                // 起始值都为 0 ,乘以 100 都是为 0 ,然后相加,随着循环次数的增长,自然的得到了每个JLable的宽高距离
                imagesJlb.setBounds(50 + j * 100,90 + i * 100,100,100);
                frame.getContentPane().add(imagesJlb);
            }
        }
        
        // 将背景图插入到底部,后插入会往下塞进去
        JLabel background = new JLabel(new ImageIcon("D:\\java_code\\Advanced-Codes\\day04-code\\image\\background.png"));
        background.setBounds(26, 30, 450, 484);
        frame.getContentPane().add(background);
        // 显示窗体
        frame.setVisible(true);
    }
}


Java进阶(第四期):(Java实战小游戏)Java中的事件和窗体 && 以及面向对象中的设计模板 && 手把手分步骤教会你完成自己的第一个Java小游戏! && 完整的游戏源代码_第8张图片

9.3 使用继承改造代码

Java进阶(第四期):(Java实战小游戏)Java中的事件和窗体 && 以及面向对象中的设计模板 && 手把手分步骤教会你完成自己的第一个Java小游戏! && 完整的游戏源代码_第9张图片

代码示例:

  • 首先先创建一个类,里面写上我们单独封装的功能代码,然后通过继承窗体类
package com.liujintao.stonepuzzle;

import javax.swing.*;

public class MainFrame extends JFrame {
    // 创建资源图片的二维数组
    int[][] arr = {
        {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 () {

        // 设置窗体
        // 设置窗体大小
        setSize(514, 595);
        // 设置窗体关闭模式
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        // 设置窗体标题
        setTitle("华容道单机版V1.0");
        // 窗口处于对上层
        setAlwaysOnTop(true);
        // 窗口居中显示
        setLocationRelativeTo(null);
        // 取消默布局
        setLayout(null);

        
    }
    
    /**
     * 此方法用于绘制界面,图片的初始化
     */
    public void paintView () {
        // 设置 JLabel 组件 将图片资源放进去
        // i 执行一次,j 执行四次,我相信都知道。只是需要注意的是。那个在一次外循环的时候,不需要变化的,我们用i,需要变化的使用j
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                JLabel imagesJlb = new JLabel(new ImageIcon("D:\\java_code\\Advanced-Codes\\day04-code\\image\\" + arr[i][j] + ".png"));
                // 起始值都为 0 ,乘以 100 都是为 0 ,然后相加,随着循环次数的增长,自然的得到了每个JLable的宽高距离
                imagesJlb.setBounds(50 + j * 100,90 + i * 100,100,100);
                // 访问父类中的这个方法,然而就一个,super直接省略
                getContentPane().add(imagesJlb);
            }
        }
        
        // 将背景图插入到底部,后插入会往下塞进去
        JLabel background = new JLabel(new ImageIcon("D:\\java_code\\Advanced-Codes\\day04-code\\image\\background.png"));
        background.setBounds(26, 30, 450, 484);
        getContentPane().add(background);
    }

}

测试类创建调用MainFrame类构造器

package com.liujintao.stonepuzzle;

import javax.swing.*;

public class Test {
    public static void main(String[] args) {
        // 调用类构造器
        new MainFrame();
    }
}


9.4 打乱二维数组(打乱界面)

Java进阶(第四期):(Java实战小游戏)Java中的事件和窗体 && 以及面向对象中的设计模板 && 手把手分步骤教会你完成自己的第一个Java小游戏! && 完整的游戏源代码_第10张图片

    /**
     * 初始化数据,打乱二维数组
     */
    public void initData () {
        Random r = new Random();
        // 遍历二维数组
        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr.length; j++) {
                // arr[i][j]
                int randomX = r.nextInt(4);
                int randomY = r.nextInt(4);
                int temp = arr[i][j];
                arr[i][j] = arr[randomX][randomY];
                arr[randomX][randomY] = temp;
            }
        }
    }
  • 然后再 MainFrame 类中的构造器 中调用 initData 方法即可!

Java进阶(第四期):(Java实战小游戏)Java中的事件和窗体 && 以及面向对象中的设计模板 && 手把手分步骤教会你完成自己的第一个Java小游戏! && 完整的游戏源代码_第11张图片

9.5 给窗体注册监听器,添加点击事件
  • 这里将 MainFrame 自己实现了接口 KeyListener,然后注册监听的饿时候,使用this作为窗体对象也就是自己注册一个监听器,然后将自己作为实现类,使用this,实现了接口作为实参传递进去。

        // 注册事件监听
        this.addKeyListener(this);

    // 键盘事件处理方法
    @Override
    public void keyPressed(KeyEvent e) {
        // 键盘按下触发
        int keycode = e.getKeyCode();
        move(keycode);
    }
    
    /**
     * 此方法处理移动业务
     * @param keycode
     */
    private static void move(int keycode) {
        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 keyReleased(KeyEvent e) {
        // 键盘弹起触发
    }
    @Override
    public void keyTyped(KeyEvent e) {
        // 类型触发
    }
9.6 获取到二维数组的 0 号索引
    int column;     // 二维数组的 列号
    int row;        // 二位数组的 行号
        // 获取到二维数组中的 0 号元素
        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr.length; j++) {
                if (arr[i][j] == 0) {
                    column = i;
                    row = j;
                }
            }
        }

Java进阶(第四期):(Java实战小游戏)Java中的事件和窗体 && 以及面向对象中的设计模板 && 手把手分步骤教会你完成自己的第一个Java小游戏! && 完整的游戏源代码_第12张图片

9.7 石块的基本移动

Java进阶(第四期):(Java实战小游戏)Java中的事件和窗体 && 以及面向对象中的设计模板 && 手把手分步骤教会你完成自己的第一个Java小游戏! && 完整的游戏源代码_第13张图片

        // !!! 每次绘制界面前,做一次清空。然后移动在绘制,就不会往上一次的下面塞进去了。
        super.getContentPane().removeAll();		// super可以省略

		// 绘制视图的逻辑

        // !!! 刷新操作!
        super.getContentPane().repaint();
  • 当事件被触发的时候就会执行监听器里面的move’方法
     // 键盘事件处理方法
     @Override
     public void keyPressed(KeyEvent e) {
         // 键盘按下触发
         int keycode = e.getKeyCode();
         move(keycode);
         // 事件触发后,重新绘制界面
         paintView();
     }
    
    /**
     * 此方法处理移动业务
     * @param keycode
     */
    private void move(int keycode) {
        if (keycode == 37) {
            // 空白块的位置
            int temp = arr[row][column];
            arr[row][column] = arr[row][column + 1];
            arr[row][column + 1] = temp;
            //交换完毕后,我们空白块(0号)右移就得将column加一
            column++;
        } else if (keycode == 38) {
            // 空白块的位置
            int temp = arr[row][column];
            arr[row][column] = arr[row + 1][column];
            arr[row + 1][column] = temp;
            row++;
        } else if (keycode == 39) {
            // 空白块的位置
            int temp = arr[row][column];
            arr[row][column] = arr[row][column - 1];
            arr[row][column - 1] = temp;
            column--;
        }else if (keycode == 40) {
            // 空白块的位置
            int temp = arr[row][column];
            arr[row][column] = arr[row - 1][column];
            arr[row - 1][column] = temp;
            row--;
        } else if (keycode == 90) {
            // z键胜利!
            arr = new int[][] {
                    {1, 2, 3, 4},
                    {5, 6, 7, 8},
                    {9, 10, 11, 12},
                    {13, 14, 15, 0},
            };
        }
9.8 下标索引越界解决

Java进阶(第四期):(Java实战小游戏)Java中的事件和窗体 && 以及面向对象中的设计模板 && 手把手分步骤教会你完成自己的第一个Java小游戏! && 完整的游戏源代码_第14张图片

            if (column == 3) {
                return;
            }
            if (row == 3) {
                return;
            }
            if (column == 0) {
                return;
            }
            if (column == 0) {
                return;
            }
  • 在移动位移前进行判断
9.9 游戏胜负判断

Java进阶(第四期):(Java实战小游戏)Java中的事件和窗体 && 以及面向对象中的设计模板 && 手把手分步骤教会你完成自己的第一个Java小游戏! && 完整的游戏源代码_第15张图片

    // 创建一个二维数组,用于比对。如果arr 数组等于 win 表示胜利返回true
    int[][] win = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12},
        {13, 14, 15, 0},
    };
  • 因为我们每次移动的时候,都需要判断。所以为了不造成性能问题,所以我将该数组放到了全局作用域下。

  • 定义一个判断是否胜利的方法,返回值为 boolean类型

    /**
     * 判断游戏是否胜利
     *
     */
    public boolean victory () {
        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr.length; j++) {
                if (arr[i][j] != win[i][j]) {
                    return false;
                }
            }
        }
        // 不跑if 证明都相等!游戏胜利
        return true;
    }
  • 在 paintView 方法里面每次清空界面后,我就调用 victory 方法,如果成立表示胜利,我们加载胜利图片然后,在将成功的石头块重新加载,然后往我们胜利的图片下面塞进去!
        // 每次移动移动之前都判断一下游戏是否胜利
        if (victory()) {
            // 返回true,显示胜利!将资源加载到界面中
            JLabel jb = new JLabel(new ImageIcon("D:\\java_code\\Advanced-Codes\\day04-code\\image\\win.png"));
            jb.setBounds(124, 230, 266, 88);
            getContentPane().add(jb);
        }
  • 最后,优化一下,如果成功了,我们就不能在移动石头方块了,解决方法就是,在移动方法执行后,第一时间判断是否胜利,胜利直接return该方法,不让移动即可!
        // 游戏胜利,该方法里面的移动逻辑则不能执行了(只有在非胜利状态下,才能执行)
        if (victory()) {
            return;
        }
9.10 统计步数、重新游戏!
  • 统计步数:这里就是定义一个成员变量,分别在移动的事件中,都进行 count++ ,然后再加一个JLabel
  • 重新游戏:就是将count 归零,然后创建JButton,绘制界面,然后注册点击事件,重新打乱石头方块,并且重新绘制界面!
        // 设置步数界面显示
        JLabel scoreLabel = new JLabel("当前步数:" + count);
        scoreLabel.setBounds(50, 20, 100, 20);
        getContentPane().add(scoreLabel);
        
        // 重新游戏界面显示
        JButton newStart = new JButton("重新开始");
        newStart.setBounds(366, 20, 100, 20);
        newStart.setFocusable(false);
        getContentPane().add(newStart);
        newStart.addActionListener(e -> {
                count = 0;  // 步数归零
                initData(); // 打乱石头方块
                paintView(); // 重新加载界面
        });

十、完整的游戏源代码如下:

10.1 测试类代码:

package com.liujintao.stonepuzzle;

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


10.2 主要逻辑类

package com.liujintao.stonepuzzle;


import javax.swing.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Random;

public class MainFrame extends JFrame implements KeyListener {
    // 创建资源图片的二维数组
    int[][] arr = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12},
        {13, 14, 15, 0},
    };
    // 创建一个二维数组,用于比对。如果arr 数组等于 win 表示胜利返回true
    int[][] win = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12},
        {13, 14, 15, 0},
    };
    
    int column;     // 二维数组的 列号
    int row;        // 二位数组的 行号
    int count = 0;  // 统计步数变量
    
    
    // 通过构造方法调用各个功能模块,只要我这个类被创建,那么就执行!
    public MainFrame() {
        // 初始化界面
        initFrame();
        
        // 打乱界面
        initData();
        
        // 绘制界面
        paintView();
        
        // 注册事件监听
        this.addKeyListener(this);
        
        // 显示窗体
        setVisible(true);
    }
    
    /**
     * 初始化数据,打乱二维数组
     */
    public void initData () {
        Random r = new Random();
        // 遍历二维数组
        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr.length; j++) {
                // arr[i][j]
                int randomX = r.nextInt(4);
                int randomY = r.nextInt(4);
                int temp = arr[i][j];
                arr[i][j] = arr[randomX][randomY];
                arr[randomX][randomY] = temp;
            }
        }
        
        // 获取到二维数组中的 0 号元素
        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr.length; j++) {
                if (arr[i][j] == 0) {
                    column = i;
                    row = j;
                }
            }
        }
    }
    
    
    /**
     * 初始化界面
     */
    public void initFrame () {

        // 设置窗体
        // 设置窗体大小
        setSize(514, 595);
        // 设置窗体关闭模式
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        // 设置窗体标题
        setTitle("华容道单机版V1.0");
        // 窗口处于对上层
        setAlwaysOnTop(true);
        // 窗口居中显示
        setLocationRelativeTo(null);
        // 取消默布局
        setLayout(null);

        
    }
    
    /**
     * 此方法用于绘制界面,图片的初始化
     */
    public void paintView () {
        // !!! 每次绘制界面前,做一次清空。然后移动在绘制,就不会往上一次的下面塞进去了。
        getContentPane().removeAll();
        
        // 每次移动移动之前都判断一下游戏是否胜利
        if (victory()) {
            // 返回true,显示胜利!将资源加载到界面中
            JLabel jb = new JLabel(new ImageIcon("D:\\java_code\\Advanced-Codes\\day04-code\\image\\win.png"));
            jb.setBounds(124, 230, 266, 88);
            getContentPane().add(jb);
        }
        
        // 设置步数界面显示
        JLabel scoreLabel = new JLabel("当前步数:" + count);
        scoreLabel.setBounds(50, 20, 100, 20);
        getContentPane().add(scoreLabel);
        
        // 重新游戏界面显示
        JButton newStart = new JButton("重新开始");
        newStart.setBounds(366, 20, 100, 20);
        newStart.setFocusable(false);
        getContentPane().add(newStart);
        newStart.addActionListener(e -> {
                count = 0;  // 步数归零
                initData(); // 打乱石头方块
                paintView(); // 重新加载界面
        });
        
        
        // 设置 JLabel 组件 将图片资源放进去
        // i 执行一次,j 执行四次,我相信都知道。只是需要注意的是。那个在一次外循环的时候,不需要变化的,我们用i,需要变化的使用j
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                JLabel imagesJlb = new JLabel(new ImageIcon("D:\\java_code\\Advanced-Codes\\day04-code\\image\\" + arr[i][j] + ".png"));
                // 起始值都为 0 ,乘以 100 都是为 0 ,然后相加,随着循环次数的增长,自然的得到了每个JLable的宽高距离
                imagesJlb.setBounds(50 + j * 100,90 + i * 100,100,100);
                // 访问父类中的这个方法,然而就一个,super直接省略
                getContentPane().add(imagesJlb);
            }
        }
        
        
        // !!! 刷新操作!
        getContentPane().repaint();
        
        
        // 将背景图插入到底部,后插入会往下塞进去
        JLabel background = new JLabel(new ImageIcon("D:\\java_code\\Advanced-Codes\\day04-code\\image\\background.png"));
        background.setBounds(26, 30, 450, 484);
        getContentPane().add(background);
    }
    
    /**
     * 判断游戏是否胜利
     *
     */
    public boolean victory () {
        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr.length; j++) {
                if (arr[i][j] != win[i][j]) {
                    return false;
                }
            }
        }
        // 不跑if 证明都相等!游戏胜利
        return true;
    }
    
    
    // 键盘事件处理方法
    @Override
    public void keyPressed(KeyEvent e) {
        // 键盘按下触发
        int keycode = e.getKeyCode();
        move(keycode);
        // 事件触发后,重新绘制界面
        paintView();
    }
    
    /**
     * 此方法处理移动业务
     * @param keycode
     */
    private void move(int keycode) {
        // 游戏胜利,该方法里面的移动逻辑则不能执行了(只有在非胜利状态下,才能执行)
        if (victory()) {
            return;
        }
        
        
        if (keycode == 37) {
            if (column == 3) {
                return;
            }
            // 空白块的位置
            int temp = arr[row][column];
            arr[row][column] = arr[row][column + 1];
            arr[row][column + 1] = temp;
            //交换完毕后,我们空白块(0号)右移就得将column加一
            column++;
            count++;
        } else if (keycode == 38) {
            if (row == 3) {
                return;
            }
            // 空白块的位置
            int temp = arr[row][column];
            arr[row][column] = arr[row + 1][column];
            arr[row + 1][column] = temp;
            row++;
            count++;
        } else if (keycode == 39) {
            if (column == 0) {
                return;
            }
            // 空白块的位置
            int temp = arr[row][column];
            arr[row][column] = arr[row][column - 1];
            arr[row][column - 1] = temp;
            column--;
            count++;
        }else if (keycode == 40) {
            if (row == 0) {
                return;
            }
            // 空白块的位置
            int temp = arr[row][column];
            arr[row][column] = arr[row - 1][column];
            arr[row - 1][column] = temp;
            row--;
            count++;
        } else if (keycode == 90) {
            // z键胜利!覆盖被打乱的二维数组
            arr = new int[][] {
                    {1, 2, 3, 4},
                    {5, 6, 7, 8},
                    {9, 10, 11, 12},
                    {13, 14, 15, 0},
            };
        }
    }
    
    
    // 下面两种方法不常用,我们选择上面的按下触发事件即可了。
    @Override
    public void keyReleased(KeyEvent e) {
        // 键盘弹起触发
    }
    @Override
    public void keyTyped(KeyEvent e) {
        // 类型触发
    }
    
}

  • 源代码看不懂的,可以参考我 Java进阶专栏里面的第四期文章中的第九点。分步骤手把手教你。需要资源的私信我发送,谢谢大家!!!

你可能感兴趣的:(Java项目,Java进阶,java,游戏,开发语言)