技术需求
Java基础封装、继承、多态。
代码实现
创建一个子类继承自父类JFrame,好处是继承到父类的所有方法,直接使用,更为方便。
public class MyFrame{}
创建方法用于设置窗口参数
public void window(){
// 设置窗口大小
setSize(514,594);
// 设置窗口标题
setTitle("单机游戏v1.0");
// 设置窗口居中显示
setLocationRelativeTo(null);
// 设置窗口置顶
setAlwaysOnTop(true);
// 设置程序关闭模式为窗口关闭
setDefaultCloseOperation(MyFrameDemo.EXIT_ON_CLOSE);
// 取消默认布局
setLayout(null);
}
图片本身不能移动,将图片映射为二维数组下标,数组下标的改变实际上就模拟了图片的移动。
程序每次运行都需要打乱图片的摆放顺序,这里采取的办法是:将图片编号,然后把图片的序号存到一维数组中,打乱一维数组中的数据,然后把一维数组的数据再赋值给二维数组。
// 创建成员变量二维数组,用来存放随机生成的图片
int[][] arr = new int[4][4];
// 创建顺序正确的二维数组,用于校验游戏是否成功
int[][] win = {
{0,1,2,3},
{4,5,6,7},
{8,9,10,11},
{12,13,14,15}
};
// 创建成员变量行、列(用于记录空白图片的位置)、步数(用来统计步数)
int row;
int column;
int count = 0;
创建方法将二维数组随机化,以模拟图片打乱
public void initData(){
Random random = new Random();
// 用来保存图片的正确顺序
int[] a = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
// 打乱顺序
for (int i = 0; i < a.length; i++) {
int index = random.nextInt(a.length);
int temp = a[i];
a[i] = a[index];
a[index] = temp;
}
// 一位数组下标计数器
int index = 0;
// 将打乱好的数据存入二位数组中
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
arr[i][j] = a[index];
// 定位空白图片位置
if (arr[i][j] == 0){
row = i;
column = j;
}
// 更新一维数组小标
index++;
}
}
}
接下来完成图片的加载,上面我们已经将图片的名字编号,然后将打乱顺序的编号存入了二维数组中,接下来利用这些编号,将图片加载进容器内并显示出来。
// 加载图片
public void view(){
// 清除容器内的所有组件,后面图片移动时我们要刷新页面,
// 如果页面中存在有组件的话,后加载的组件会被先加载的组件覆盖
// JFrame中的重合问题采用的是先来先显示
getContentPane().removeAll();
// 创建标签用来显示移动的步数
JLabel countLabel = new JLabel("一共走了" + count + "步");
// 设置标签大小
countLabel.setBounds(50,20,100,20);
// 将标签加载到容器中(因为取消了默认布局,必须手动加载)
getContentPane().add(countLabel);
// 加载图片
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
// 创建图片对象用于存放图片,注意图片的路径的\要写\\,避免转义
// 注意:图片的名字已映射为二位数组中的数据,所以图片的名字要
// 用二维数组替换
// 如:E:\\image\\" + arr[i][j] + ".png
ImageIcon imageIcon = new ImageIcon("图片的全路径");
// 创建标签,将图片对象添加进去
JLabel imageLabel = new JLabel(imageIcon);
// 设置标签大小
// 将每个图片的显示位置动态设置,体现出我们设置二维数组的便捷性
imageLabel.setBounds(50 + j*100, 90 + i*100, 100, 100);
// 将标签添加进容器
getContentPane().add(imageLabel);
}
}
// 添加背景图片(为啥要将背景图片放到下面?知道的可以在评论区留言哦)
ImageIcon imageIcon = new ImageIcon("背景图片全路径");
JLabel backgroundLabel = new JLabel(imageIcon);
getContentPane().add(backgroundLabel);
// 获取当前容器对象,重新编辑组件(这行代码的作用?)
getContentPane().repaint();
}
到这完成了游戏界面的加载工作,我们希望创建对象时能自动加载界面,所以需要再空参构造器中进行初始化工作。
// 构造方法完成初始化
public MyFrame(){
// 初始化窗口
showView();
// 数据初始化
initData();
// 初始化图片
printView();
// 注册键盘监听器(这里用的this就体现出继承的好处了)
this.addKeyListener(this);
// 设置窗口可见
setVisible(true);
}
本质上图片是不能发生移动的,我们将图片映射到了二位数组中,就可以通过改变二维数组的下边来模拟移动操作,比如往左移,那么只需要将两个数组的数据互换就行。而每次移动都要去判断是否游戏胜利。
// 完成状态
public boolean check(){
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
// 对比当前二位数组和预设二位数组中的数据是否相同
if (arr[i][j] != win[i][j]){
return false;
}
}
}
return true;
}
通过键盘移动图片,需要对键盘设置监听器,需要实现键盘监听器接口
// 键盘监听
@Override
public void keyReleased(KeyEvent e) {
move(e.getKeyCode());
// 刷新页面
printView();
}
// 键盘监听
public void move(int key){
// 胜利
if (check()){
return;
}
// 左
if (key == 37){
if (column != 3){
arr[row][column] = arr[row][column + 1];
arr[row][column + 1] = 0;
column++;
count++;
}
// 上
} else if (key == 38) {
if (row != 3){
arr[row][column] = arr[row + 1][column];
arr[row + 1][column] = 0;
row++;
count++;
}
// 右
} else if (key == 39) {
if (column != 0){
arr[row][column] = arr[row][column - 1];
arr[row][column - 1] = 0;
column--;
count++;
}
// 下
} else if (key == 40) {
if (row != 0) {
arr[row][column] = arr[row - 1][column];
arr[row - 1][column] = 0;
row--;
count++;
}
} else if (key == 90){
arr = new int[][]{
{0, 1, 2, 3},
{4, 5, 6, 7},
{8, 9, 10, 11},
{12, 13, 14, 15}
};
}
}
// 下面方法用不到就不管
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
}
完整代码
MyFrame类
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Random;
public class MyFrame extends JFrame implements KeyListener, ActionListener {
// 二维数组控制图片编号
int[][] arr = new int[4][4];
int[][] win = {
{0,1,2,3},
{4,5,6,7},
{8,9,10,11},
{12,13,14,15}
};
// 行
int row;
// 列
int column;
// 步数
int count = 0;
// 构造方法完成初始化
public MyFrame(){
// 初始化窗口
showView();
// 数据初始化
initData();
// 初始化图片
printView();
// 初始化菜单
Menu();
// 注册键盘监听器
this.addKeyListener(this);
// 设置窗口可见
setVisible(true);
}
// 菜单
public void Menu(){
// 创建菜单对象
JMenuBar jMenuBar = new JMenuBar();
// 创建栏目对象
JMenu jMenu1 = new JMenu("功能");
JMenu jMenu2 = new JMenu("关于");
// 创建条目对象
JMenuItem jMenuItem = new JMenuItem("Replay");
// 条目添加进栏目
jMenu1.add(jMenuItem);
// 栏目添加进菜单
jMenuBar.add(jMenu1);
jMenuBar.add(jMenu2);
// 菜单添加进窗口
this.setJMenuBar(jMenuBar);
// 给条目添加监听
jMenuItem.addActionListener(this);
}
// 二位数组随机化
public void initData() {
Random random = new Random();
int[] arr1 = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
for (int i = 0; i < arr.length; i++) {
int index = random.nextInt(arr1.length);
int temp = arr1[i];
arr1[i] = arr1[index];
arr1[index] = temp;
}
int index = 0;
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
arr[i][j] = arr1[index];
if (arr[i][j] == 0){
row = i;
column = j;
}
index++;
}
}
}
// 窗口设置
public void showView(){
// 设置窗口大小
setSize(514,594);
// 设置关闭模式
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 设置置顶显示
setAlwaysOnTop(true);
// 设置居中显示
setLocationRelativeTo(null);
// 设置窗口标题
setTitle("石头迷阵v1.0单机版");
// 取消默认布局
setLayout(null);
}
// 界面加载
public void printView(){
// 清除界面
getContentPane().removeAll();
// 胜利,输出图片
if (check()){
JLabel winLabel = new JLabel(new ImageIcon("E:\\黑马程序员\\image\\image\\win.png"));
winLabel.setBounds(124,230,266,88);
getContentPane().add(winLabel);
}
// 步数加载
JLabel jLabel = new JLabel("步数:" + count);
jLabel.setBounds(50,20,100,20);
getContentPane().add(jLabel);
// 添加图片
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
//这个地址要根据你自己的地址来写
JLabel image = new JLabel(new ImageIcon("E:\\image\\"+arr[i][j]+".png"));
image.setBounds(50 + j*100, 90 + i*100, 100 ,100);
getContentPane().add(image);
}
}
// 背景图片
JLabel backgroung = new JLabel(new ImageIcon("E:\\image\\background.png"));
backgroung.setBounds(26,30,450,484);
getContentPane().add(backgroung);
// 获取当前容器对象,重新编辑组件
getContentPane().repaint();
}
// 键盘监听
@Override
public void keyReleased(KeyEvent e) {
move(e.getKeyCode());
// 刷新页面
printView();
}
// 完成状态
public boolean check(){
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
if (arr[i][j] != win[i][j]){
return false;
}
}
}
return true;
}
// 键盘监听
public void move(int key){
// 胜利
if (check()){
return;
}
// 左
if (key == 37){
if (column != 3){
arr[row][column] = arr[row][column + 1];
arr[row][column + 1] = 0;
column++;
count++;
}
// 上
} else if (key == 38) {
if (row != 3){
arr[row][column] = arr[row + 1][column];
arr[row + 1][column] = 0;
row++;
count++;
}
// 右
} else if (key == 39) {
if (column != 0){
arr[row][column] = arr[row][column - 1];
arr[row][column - 1] = 0;
column--;
count++;
}
// 下
} else if (key == 40) {
if (row != 0) {
arr[row][column] = arr[row - 1][column];
arr[row - 1][column] = 0;
row--;
count++;
}
} else if (key == 90){
arr = new int[][]{
{0, 1, 2, 3},
{4, 5, 6, 7},
{8, 9, 10, 11},
{12, 13, 14, 15}
};
}
}
//
@Override
public void keyTyped(KeyEvent e) {
}
//
@Override
public void keyPressed(KeyEvent e) {
}
@Override
public void actionPerformed(ActionEvent e) {
// 初始化数据
initData();
// 步数初始化
count = 0;
// 初始化图片图片
printView();
}
}
测试类
public class Test {
public static void main(String[] args) {
new MyFrame();
}
}
相关图片
可以做任何自己想做的拼图游戏,根据大小自己切割。
上述代码用到的图片:
百度网盘
链接:https://pan.baidu.com/s/1FCoXXOm6spF9qsdfjTWYug
提取码:785g
(上述代码为简易版,供学习参考,如有不足欢迎及时指正)