目录
完整的实现效果
使用Java实现拼图游戏。界面使用的是JFrame实现。
一、准备项目结构
1.1 主方法
在我们实现代码之前,先把项目结构构建好。我们以一个名为App的类作为运行的主方法,在其中调用游戏界面,我们将所有的游戏业务都实现在名为GameJframe的类中。
1.2Jframe界面包
在这里面,我们实现了三个类,第一个GameJframe是我们的游戏界面,第二个LoginJframe是登陆界面,第三个是RegisterJframe注册界面。我们在这里就不实现注册界面与登陆界面了,我们只讲解游戏界面的实现。
准备好这样的结构之后,我们就可以开始进行代码的书写了。
二、代码的实现
下面的内容,我将逐步分析代码的组成与功能,不能直接看到完整的代码,完整的代码放在了此目录的最后,在目录上点击 2.2.4.6 完整代码 可以查看。
2.1 主方法
import Jframe.GameJframe;
//拼图游戏
public class App {
public static void main(String[] args) {
new GameJframe();
}
}
我们调用Jframe中的GameJframe类,来创建一个新的Jframe界面用来实现我们的游戏界面,其余的登录界面与注册界面暂不实现。所以主方法就非常简单,只需这一步即可。
2.2 GameJframe
2.2.1 资源包引用
首先,我们声明一下包,与要引入的资源,在使用idea开发的过程中自动添加,不需要刻意关注,如果使用的编译器没有此功能,自己在开发的过程中搜索添加即可。
package Jframe;
import javax.swing.*;
import javax.swing.border.BevelBorder;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Arrays;
import java.util.Random;
2.2.2 构建GameJframe类
之后呢,我们构建GameJframe类。因为我们要在这里面实现窗体,所以我们继承JFrame类。又因为我们在使用游戏的时候,需要用到鼠标与键盘操作,所以我们再写入KeyListener键盘接听接口,与ActionListener接口,由于我们不需要太复杂的鼠标操作,只需点击即可,所我没有使用MouseListener。
public class GameJframe extends JFrame implements KeyListener, ActionListener {
}
2.2.3 GameJframe的成员变量
来思考需要用到的成员变量,很多东西在我们将要实现的方法中会用到,比如存放各块拼图的二维数组,用来计步数的变量。游戏菜单的各项功能。判断是否胜利的数组,这个数组保存着我们图片的正确顺序。
2.2.3.1 计数器
int counter = 0;
2.2.3.2 二维数组
由于我们只实现了4*4的游戏,所以只需要创建一个4*4的二维数组即可,需要别的玩法可自行创建
int[][] data = new int[4][4];
2.2.3.3 游戏菜单
游戏菜单的具体逻辑是这样的,JMenuBar包含JMenu,JMenu包含着JMenuItem
JMenuBar是整个菜单,所以我们从JMeun开始实现
JMenu functionJMenu = new JMenu("功能");
JMenu aboutJMenu = new JMenu("关于我们");
在功能中,我们要实现更换图片,但是更换图片也需要选项,所以,更换图片也是一个包含JMenuItem的JMenu,再把更换图片的这个JMenu加到功能这个JMenu中即可,所以更换图片JMenu中的JMenu。
JMenu changeImage = new JMenu("更换图片");
//我们这里将崩坏星穹铁道的图标设置在游戏中
//或许我们还可以将其他的关于崩铁的图片设置在里面,所以
//我们直接把“崩铁”做成一个菜单
JMenu Star_rail = new JMenu("崩铁");
接下来,我们实现JMenuItem,也就是上述的各种菜单中的选项。
//这些是功能中的选项
JMenuItem replayItem = new JMenuItem("重新游戏");
JMenuItem reLoginItem = new JMenuItem("重新登陆");
JMenuItem closeItem = new JMenuItem("关闭游戏");
JMenu changeImage = new JMenu("更换图片");
//这些是关于我们中的选项
//公众号只是举个例子,你可以是先别的来这里
JMenuItem accountItem = new JMenuItem("公众号");
//这个是更改图片中的崩铁中的选项
//创建崩铁下的选项
JMenuItem March = new JMenuItem("三月七");
JMenuItem FireFly = new JMenuItem("流萤");
用以判断的胜利数组,存放着正确的顺序(最后一项为空,也就是拼图游戏中空白的格子)。
int[][] win = {
{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
{13,14,15,0}
};
2.2.3.4 图片路径
为了方便代码的书写,我们干脆一劳永逸,直接把路径定义为变量,之后的代码只需要改变这个量即可。
//记录16块图片路径 默认为Star_rail 后续可以更改
String path1 = "image/Star_rail/(";
//记录完整图片路径
String pathAll = "image/Star_rail/Start Rail.jpg";
这下我们的GameJframe的成员变量就书写完毕了。
2.2.4 GameJframe的方法
2.2.4.1 加载数据方法
写到这里,我们应该加载数据了,其中包括,写一个0-15的数组,将其打乱,将打乱的数组放入我们先前准备好的二维数组。一些必要的注释已经写在里面了。
private void initData(){
//需求:把0-15的数据打乱顺序
//然后再按照4各一组的方式添加到二维数组中
int[] tempArr = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
//打乱数组中的数字
Random r = new Random();
for (int i = 0; i < tempArr.length; i++) {
//随机索引
int index = r.nextInt(tempArr.length);
int temp = tempArr[i];
tempArr[i] = tempArr[index];
tempArr[index] = temp;
}
//给二维数组添加数据
for(int i :tempArr){
if(tempArr[i]==0){
x = i / 4;
y = i % 4;
}
data[i / 4][i % 4] = tempArr[i];
}
}
2.2.4.2 判断胜利的方法
我们在不断的交换图片过程中,其实就是改变data中数据的过程,判断胜利,只需要比较data与我们的win数组是否相同即可。
private boolean check_win(){
return Arrays.deepEquals(data, win);
}
2.2.4.3加载图片的方法
写到这里,我们要开始加载图片了,结合了我们先前打乱的二维数组来插入图片,所以没我们的图片也要打乱,那如何建立两者之间的联系呢?做到这里的读者,请务必将分好的16张图片,按照图片顺序,进行1-16的命名,这样的话,我们只需要在路径中,运用数字,将图片添加进来。
再定义一个int num;获取二维数组的值,我们就可以按照num添加照片了。比如,在我的16张图片中,均命名为 (1).jpg…………,(16).jpg,这样我们添加的时候,就可以添加
"image/("+num+").jpg"; 那这样,他就会分别添加"image/(1).jpg","image/(2).jpg","image/(3).jpg"…………,最终添加到"image/(15).jpg"。而且,因为数组只添加0-15,空下来的0,由于找不到对应的图片,就会打印空白,正好对应了我们拼图中的空白。接下来的移动,无非就是这个空白图片与相邻图片的交换而已。
我们还需要做一个胜利图标,我的可能跟你们的不一样,所以你们实现完之后,要根据图片大小,修改位置。
private void initImage() {
this.getContentPane().removeAll();
if(check_win()){
//显示胜利图标
JLabel winJLabel = new JLabel(new ImageIcon("image/winJframe.jpg"));
winJLabel.setBounds(173,-5,210,80);
this.getContentPane().add(winJLabel);
}
JLabel Stepcounter = new JLabel("步数:"+counter);
Stepcounter.setBounds(50,30,100,20);
this.getContentPane().add(Stepcounter);
for (int j = 0; j < 4; j++) {
for (int i = 0; i < 4; i++) {
//获取打乱二维数组序号
int num = data[j][i];
JLabel jLabel = new JLabel(new ImageIcon(path1+num+").jpg"));
jLabel.setBounds(105 * i+83, 105*j+134, 105, 105);
//给图片加边框
jLabel.setBorder(new BevelBorder(BevelBorder.LOWERED));
this.getContentPane().add(jLabel);
}
}
//刷新界面
this.getContentPane().repaint();
}
2.2.4.4 创建菜单界面
很多注意事项都放在了注释中,需要额外注意的是,
private void initJMenuBar() {
//创建菜单
JMenuBar jMenuBar = new JMenuBar();
//将Item添加到JMenu中
functionJMenu.add(replayItem);
functionJMenu.add(reLoginItem);
functionJMenu.add(closeItem);
functionJMenu.add(changeImage);
aboutJMenu.add(accountItem);
//给更改图片加菜单
changeImage.add(Star_rail);
//给崩铁加选项 其中March表示的是崩铁的图标,FireFly是流萤的图片
Star_rail.add(March);
Star_rail.add(FireFly);
//将JMenu添加到JMenuBar中
jMenuBar.add(functionJMenu);
jMenuBar.add(aboutJMenu);
//给游戏功能绑定事件
replayItem.addActionListener(this);
reLoginItem.addActionListener(this);
closeItem.addActionListener(this);
accountItem.addActionListener(this);
//给选项添加监听
FireFly.addActionListener(this);
March.addActionListener(this);
//将菜单放在界面中
this.setJMenuBar(jMenuBar);
}
2.2.4.5 键盘监听重构方法
keyPressed表示按住不动某个键要实现的代码,根据这个逻辑,我们可以设置按住A不动时,我们加载整张图片供玩家查看。这样会方便玩家解出。
我们的逻辑就是,按住A时,删除界面所有图片,添加完整的一张图,刷新界面。A的编号是65,所以我们keyPressed中参数e在按住A的时候等于65。
@Override
public void keyPressed(KeyEvent e) {
int code = e.getKeyCode();
if(code == 65){
//把界面中所有图片删除
this.getContentPane().removeAll();
//加载第一张完整的图片
JLabel all = new JLabel(new ImageIcon(pathAll));
all.setBounds(83,134,420,420);
this.getContentPane().add(all);
//刷新界面
this.getContentPane().repaint();
}
}
接下来就是移动图片了keyReleased方法,可以检测我们是否松开了某个键,所以常用来实现功能,键盘上的左,对应的是37,上是38,右是39,下是40,每按下相应的按钮,就交换对应的两张图片,每按一次就判断游戏是否胜利,胜利了就返回方法。
@Override
public void keyReleased(KeyEvent e) {
//判断游戏游戏是否胜利
if(check_win()){
return;
}
//对应的编码 左:37 上:38 右:39 下:40
//
int code = e.getKeyCode();
if(code == 37){
data[x][y] = data[x][y+1];
data[x][y+1] = 0;
y = y+1;
counter++;
initImage();
}else if(code == 38){
data[x][y] = data[x+1][y];
data[x+1][y] = 0;
x = x+1;
counter++;
initImage();
}else if(code == 39){
data[x][y] =data[x][y-1];
data[x][y-1] = 0;
y=y-1;
counter++;
initImage();
}else if(code == 40){
data[x][y] = data[x-1][y];
data[x-1][y] = 0;
x = x-1;
counter++;
initImage();
}else if(code == 65){
initImage();
}else if(code == 87){
data = new int[][]{
{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
{13,14,15,0}
};
initImage();
}
}
2.2.4.6 鼠标监听重构方法
@Override
public void actionPerformed(ActionEvent e) {
//获取当前被点击的对象
Object obj = e.getSource();
if(obj == replayItem){
//再次打乱
initData();
//计数器清零
counter =0;
//重新加载
initImage();
}else if(obj == reLoginItem){
//返回登陆界面
//关闭当前游戏界面
this.setVisible(false);
//打开登陆界面 这里由于我们没有实现,所以无法使用
new LoginJframe();
}else if(obj == closeItem){
System.exit(0);
}else if(obj == accountItem){
//创建一个弹窗对象
JDialog jDialog = new JDialog();
//管理图片
JLabel jLabel = new JLabel(new ImageIcon("这里放入你公众号,或者别的什么图片路径"));
//根据图片大小修改位置,我的不一定适配你的
jLabel.setBounds(0,0,258,258);
jDialog.getContentPane().add(jLabel);
jDialog.setSize(344,344);
jDialog.setAlwaysOnTop(true);
//居中
jDialog.setLocationRelativeTo(null);
//弹窗不关闭无法操作当前界面
jDialog.setModal(true);
//显示弹窗
jDialog.setVisible(true);
}else if(obj == March){
pathAll = "image/Star_rail/Start Rail.jpg";
path1 = "image/Star_rail/(";
initImage();
}else if(obj == FireFly) {
pathAll = "image/Fire fly/Fire Fly.jpg";
path1 = "image/Fire fly/(";
initImage();
}
2.2.4.6 完整代码
2.2.4.6.1 主函数
import Jframe.GameJframe;
//拼图游戏
public class App {
public static void main(String[] args) {
new GameJframe();
}
}
2.2.4.6.2 游戏界面
package Jframe;
import javax.swing.*;
import javax.swing.border.BevelBorder;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Arrays;
import java.util.Random;
public class GameJframe extends JFrame implements KeyListener, ActionListener {
//建立随机二维数组
//加载图片的时候,根据二维数组加载
int[][] data = new int[4][4];
//计数器,用来统计步数
int counter = 0;
//记录分散图片路径
String path1 = "image/Star_rail/(";
//记录完成图片路径
String pathAll = "image/Star_rail/Start Rail.jpg";
//初始化菜单选项
JMenu functionJMenu = new JMenu("功能");
JMenu aboutJMenu = new JMenu("关于我");
//创建选项下面的条目对象
JMenuItem replayItem = new JMenuItem("重新游戏");
JMenuItem reLoginItem = new JMenuItem("重新登陆");
JMenuItem closeItem = new JMenuItem("关闭游戏");
JMenu changeImage = new JMenu("更换图片");
JMenu Star_rail = new JMenu("崩铁");
//创建更改图片下的菜单
JMenuItem accountItem = new JMenuItem("公众号");
//创建崩铁下的选项
JMenuItem March = new JMenuItem("三月七");
JMenuItem FireFly = new JMenuItem("流萤");
int[][] win = {
{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
{13,14,15,0}
};
public GameJframe(){
//设置界面大小
initJFrame();
//初始化菜单
initJMenuBar();
//初始化数据
initData();
//初始化图片(根据打乱之后的结果加载图片)
initImage();
//展示整个界面
this.setVisible(true);
}
//初始化数据
//记录空白方块的位置
int x,y;
private void initData(){
//需求:把0-15的数据打乱顺序
//然后再按照4各一组的方式添加到二维数组中
int[] tempArr = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
//打乱数组中的数字
Random r = new Random();
for (int i = 0; i < tempArr.length; i++) {
//随机索引
int index = r.nextInt(tempArr.length);
int temp = tempArr[i];
tempArr[i] = tempArr[index];
tempArr[index] = temp;
}
//给二维数组添加数据
for(int i :tempArr){
if(tempArr[i]==0){
x = i / 4;
y = i % 4;
}
data[i / 4][i % 4] = tempArr[i];
}
}
//初始化图片
//先加载的图片在上方,后加载的,塞到了先加载的后方
private void initImage() {
this.getContentPane().removeAll();
if(check_win()){
//显示胜利图标
JLabel winJLabel = new JLabel(new ImageIcon("image/winJframe.jpg"));
winJLabel.setBounds(173,-5,210,80);
this.getContentPane().add(winJLabel);
}
JLabel Stepcounter = new JLabel("步数:"+counter);
Stepcounter.setBounds(50,30,100,20);
this.getContentPane().add(Stepcounter);
for (int j = 0; j < 4; j++) {
for (int i = 0; i < 4; i++) {
//获取打乱二维数组序号
int num = data[j][i];
JLabel jLabel = new JLabel(new ImageIcon(path1+num+").jpg"));
jLabel.setBounds(105 * i+83, 105*j+134, 105, 105);
//给图片加边框
jLabel.setBorder(new BevelBorder(BevelBorder.LOWERED));
this.getContentPane().add(jLabel);
}
}
//刷新界面
this.getContentPane().repaint();
}
//创建菜单界面
private void initJMenuBar() {
JMenuBar jMenuBar = new JMenuBar();
//将Item添加到JMenu中
functionJMenu.add(replayItem);
functionJMenu.add(reLoginItem);
functionJMenu.add(closeItem);
functionJMenu.add(changeImage);
aboutJMenu.add(accountItem);
//给更改图片加菜单
changeImage.add(Star_rail);
//给崩铁加选项
Star_rail.add(March);
Star_rail.add(FireFly);
//将JMenu添加到JMenuBar中
jMenuBar.add(functionJMenu);
jMenuBar.add(aboutJMenu);
//给游戏功能绑定事件
replayItem.addActionListener(this);
reLoginItem.addActionListener(this);
closeItem.addActionListener(this);
accountItem.addActionListener(this);
//给选项添加监听
March.addActionListener(this);
FireFly.addActionListener(this);
//将菜单放在界面中
this.setJMenuBar(jMenuBar);
}
//判断胜利
private boolean check_win(){
return Arrays.deepEquals(data, win);
}
private void initJFrame() {
this.setSize(603,680);
//设置界面标题
this.setTitle("拼图单机版");
//设置界面保持上层
this.setAlwaysOnTop(true);
//设置界面居中
this.setLocationRelativeTo(null);
//设置界面关闭模式
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//取消默认居中放置,之后才能按照xy轴放置
this.setLayout(null);
//给整个界面添加键盘监听事件
this.addKeyListener(this);
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
int code = e.getKeyCode();
if(code == 65){
//把界面中所有图片删除
this.getContentPane().removeAll();
//加载第一张完整的图片
JLabel all = new JLabel(new ImageIcon(pathAll));
all.setBounds(83,134,420,420);
this.getContentPane().add(all);
//刷新界面
this.getContentPane().repaint();
}
}
@Override
public void keyReleased(KeyEvent e) {
//判断游戏游戏是否胜利
if(check_win()){
return;
}
//左:37 上:38 右:39 下:40
//
int code = e.getKeyCode();
if(code == 37){
data[x][y] = data[x][y+1];
data[x][y+1] = 0;
y = y+1;
counter++;
initImage();
}else if(code == 38){
data[x][y] = data[x+1][y];
data[x+1][y] = 0;
x = x+1;
counter++;
initImage();
}else if(code == 39){
data[x][y] =data[x][y-1];
data[x][y-1] = 0;
y=y-1;
counter++;
initImage();
}else if(code == 40){
data[x][y] = data[x-1][y];
data[x-1][y] = 0;
x = x-1;
counter++;
initImage();
}else if(code == 65){
initImage();
}else if(code == 87){
data = new int[][]{
{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
{13,14,15,0}
};
initImage();
}
}
@Override
public void actionPerformed(ActionEvent e) {
//获取当前被点击的对象
Object obj = e.getSource();
if(obj == replayItem){
//再次打乱
initData();
//计数器清零
counter =0;
//重新加载
initImage();
}else if(obj == reLoginItem){
//返回登陆界面
//关闭当前游戏界面
this.setVisible(false);
//打开登陆界面
new LoginJframe();
}else if(obj == closeItem){
System.exit(0);
}else if(obj == accountItem){
//创建一个弹窗对象
JDialog jDialog = new JDialog();
//管理图片
JLabel jLabel = new JLabel(new ImageIcon("放入你公众号的图片"));
jLabel.setBounds(0,0,258,258);
jDialog.getContentPane().add(jLabel);
jDialog.setSize(344,344);
jDialog.setAlwaysOnTop(true);
//居中
jDialog.setLocationRelativeTo(null);
//弹窗不关闭无法操作当前界面
jDialog.setModal(true);
//显示弹窗
jDialog.setVisible(true);
}else if(obj == March){
pathAll = "image/Star_rail/Start Rail.jpg";
path1 = "image/Star_rail/(";
initImage();
}else if(obj == FireFly) {
pathAll = "image/Fire fly/Fire Fly.jpg";
path1 = "image/Fire fly/(";
initImage();
}
}
}
三、注意事项
没什么其他可讲的,如果出现了图片未加载出来,多半是因为你的图片路径不对。我上述的代码涉及到的路径,你可以照着写一个,但是如果你有你的图片路径,一定要找到这个图片路径,再重新写入。