一,开发步骤
1、了解拼图游戏基本功能:
拼图游戏内容由若干小图像块组成的,通过鼠标点击图像块上下左右移动,完成图像的拼凑。
2、拼图游戏交互界面设计与开发:
通过创建窗体类、菜单、中间面板和左右面板完成设计拼图的交互界面 ,实现拼图游戏的基本功能。
3、图片的加载与分割:
使用Image类实现图片的缩放,ImageIO类实现图片的读写加载,通过接口类Icon,BufferedImage类获取BufferedImage类的对象实现图片分割。
4、图片随机打乱和交换:
产生随机数 Random rand=new Random();
rand.nextInt(hs*ls)------[0,8]
具体操作:生成两个随机数表示数组下标,互换两个数组元素的位置,按钮的方法getX和getY可以获取按钮的坐标,利用按钮的单击事件的处理ActionListener可以使其图片交换。
5、判赢:
当用户移动按钮后进行判断,代码写在监听器的actionPerformed方法中,判断拼图是否成功,主要取决于每一个按钮通过索引下标获取的位置值,与当前按钮的位置值是否相同。
6、计时和计数功能的实现:
计时功能的实现主要是线程的设计,线程的定义方法:第一:继承Thread类,第二:实现Runnable接口,创建带实现接口的子类对象的Thread对象,MainJFrame实现Runnable接口,重写run方法;而计数则在主窗体中通过rp.times实现对变量的使用来计数。
7、游戏记录的保存:
当用户拼图成功后,记录当前信息到文件中,FileWriter追加写信息,FileReader完成读取数据。
程序源代码
登录界面部分(用户名:admin 密码:123)
import java.awt.Color;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
public class LoginPintu extends JFrame{
JLabel jl1,jl2,jl3,jl4;
JTextField jtf;//文本框
JPasswordField jpf;//密码
JButton jb1,jb2;
public LoginPintu() {
this.setTitle("拼图游戏");
setBounds(400,350,500,400);
//设置窗体为流式布局
setLayout(new GridLayout(20,1));
//空布局
setLayout(null);
init();
setVisible(true);
setDefaultCloseOperation(EXIT_ON_CLOSE);
jb1.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
if(jtf.getText().trim().equals("admin")&&
new String(jpf.getPassword()).trim().equals("123"))
{JOptionPane.showMessageDialog(null, "欢迎进入游戏!");
new MainJFrame();}
else if(jtf.getText().trim().length()==0||
new String(jpf.getPassword()).trim().length()==0)
{JOptionPane.showMessageDialog(null, "用户名或密码不能为空!");}
else {JOptionPane.showMessageDialog(null, "用户名或密码错误!");}
}
});
jb2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// System.exit(0);
//获取事件源对象
JButton jb=(JButton)e.getSource();
jtf.setText(jb.getText());
}
});
}
public void init() {
jl1=new JLabel("拼图游戏登录窗口");
jl2=new JLabel("用户名:");
jl3=new JLabel("密码:");
jtf=new JTextField(10);
jpf=new JPasswordField(10);
jb1=new JButton("登录");
jb2=new JButton("取消");
jl1.setBounds(150,30,200,60);
jl2.setBounds(100, 120, 180, 30);
jtf.setBounds(200, 120, 180, 30);
jl3.setBounds(100, 180, 180, 30);
jpf.setBounds(200, 180, 180, 30);
jb1.setBounds(100, 260, 100, 30);
jb2.setBounds(220, 260, 100, 30);
Font font = new Font("楷体",Font.PLAIN,25);
jl1.setFont(font);
jl1.setForeground(Color.red);
add(jl1);
add(jl2);
add(jtf);
add(jl3);
add(jpf);
add(jb1);
add(jb2);
}
public static void main(String[] args) {
new LoginPintu();
}
}
左面板部分
import java.awt.Image;
import java.net.MalformedURLException;
import java.net.URL;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
//左面板类
public class LeftJPanel extends JPanel {
JLabel jl;
int width=700;
int height=700;
//构造方法
//标签创建,指定图片,放置到面板中
public LeftJPanel(){
//左面板大小
setSize(width,height);
jl=new JLabel();
jl.setSize(width,height);
//把标签添加到面板中
this.add(jl);
}
public void init(URL url){
//绝对路径:访问文件是从盘符开始
// ImageIcon icon=new ImageIcon("D:\\1picture\\s4.jpg");
//相对路径:访问路径不是从盘符开始,可以是\,也可以是一个文件夹
// ImageIcon icon=new ImageIcon("s4.jpg");//参数是字符串的相对路径,相对于当前项目根目录
//相对路径下url的获取
// //绝对路径的url的获取
// URL url=null;
// try {
// url = new URL("file:\\D:\\1picture\\5.jpg");
// } catch (MalformedURLException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
ImageIcon icon=new ImageIcon(url);
//方法一:图片缩放
// Image image = icon.getImage();
// Image image2 = image.getScaledInstance(700, 700, 1);
// ImageIcon icon2 = new ImageIcon(image2);
// jl.setIcon(icon2);
//链式编程方式实现图片缩放
jl.setIcon(new ImageIcon(icon.getImage().getScaledInstance(width, height, 1)));
//刷新界面
validate();
}
}
右面板部分
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URL;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import jdk.jfr.events.FileWriteEvent;
//右面板实现ActionListener接口,右面板也就成为了监听器
public class RightJPanel extends JPanel implements ActionListener{
//面板的大小
int width=700;
int height=700;
//定义按钮数组
JButton[] jbs;
//设置分割的行列数
int hs=2,ls=2;
//按钮的宽度和高度,指定是小图图片缩放的尺寸
int widthbut,heightbut;
//图片原始高度宽度
int widthtp,heighttp;
//小图的原始宽度高度
int widthxt,heightxt;
//实现步数计算的变量
int times;
//空白按钮
JButton kb;
public RightJPanel(){
//面板布局是空布局
setLayout(null);
setSize(width,height);
//init();
}
//创建按钮,并放置到右面板
public void init(URL url) {
//面板组件初始化前,先清除所有已有的组件
this.removeAll();
//创建按钮数组
jbs=new JButton[hs*ls];
//为每一个按钮实现初始化
//计算按钮的宽度和高度
//面板是700*700,拆分成3*3的9个区域
//每一块区域的宽度 700/3
//每一块区域的高度 700/3
widthbut=width/ls;
heightbut=height/hs;
BufferedImage buf=null;
try {
buf = ImageIO.read(url);
//获取原图的宽度、高度
widthtp=buf.getWidth();
heighttp=buf.getHeight();
//获取小图的宽度和高度
widthxt=widthtp/ls;
heightxt=heighttp/hs;
//每一块按钮的坐标位置确定
for(int i=0;i<jbs.length;i++){
jbs[i]=new JButton();
jbs[i].setSize(widthbut,heightbut);
//jbs[i].setText(i+"");
//添加按钮前要确定坐标位置
//横坐标 i=0 0 i=1 233 i=2 466
//i=3 0 i=4 233
//纵坐标 i=3
jbs[i].setLocation((i%ls)*widthbut, i/ls*heightbut);
//jbs[i].setIcon(null);
//小图的获取
BufferedImage subimage = buf.getSubimage(i%ls*widthxt, i/ls*heightxt, widthxt, heightxt);
//小图的缩放
Image image = subimage.getScaledInstance(widthbut, heightbut, 1);
//将小图图片放置到按钮上
jbs[i].setIcon(new ImageIcon(image));
//添加按钮到右面板
add(jbs[i]);
//设置按钮不可用
jbs[i].setEnabled(false);
//设置按钮的监听,当按钮被单击,会到右面板中找actionPerformed方法执行
jbs[i].addActionListener(this);
}
jbs[hs*ls-1].setIcon(null);
kb=jbs[hs*ls-1];
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//打乱按钮在面板中显示的顺序
public void randomOrder(){
//创建随机数对象
Random rand=new Random();
//打乱多次
for(int i=0;i<hs*ls;i++){
//随机索引
int index1=rand.nextInt(hs*ls);
int index2=rand.nextInt(hs*ls);
int x1=jbs[index1].getX();
int y1=jbs[index1].getY();
int x2=jbs[index2].getX();
int y2=jbs[index2].getY();
jbs[index1].setLocation(x2, y2);
jbs[index2].setLocation(x1, y1);
jbs[i].setEnabled(true);
}
}
//按钮的单击事件执行的代码
@Override
public void actionPerformed(ActionEvent e) {
// 判断单击按钮和空白按钮是否相邻,如果相邻,则位置互换
//获取用户单击的按钮 ,通过ActionEvent e的方法gerSource获取事件源
JButton jb=(JButton)(e.getSource());
//获取单击按钮和空白按钮的坐标
int x1=jb.getX();
int y1=jb.getY();
int x2=kb.getX();
int y2=kb.getY();
//判断是否可以移动
//Math.abs(x1-x2)/widthbut + Math.abs(y1-y2)/heightbut==1
if (Math.abs(x1-x2)/widthbut + Math.abs(y1-y2)/heightbut==1){
jb.setLocation(x2, y2);
kb.setLocation(x1, y1);
times++;
}
//判断是否拼图成功
if (isWin()){
JOptionPane.showMessageDialog(null, "恭喜你,拼图成功");
//使得按钮不可用
for(int i=0;i<jbs.length;i++){
jbs[i].setEnabled(false);
}
//提示用户输入名称
//使用输入对话框
String name = JOptionPane.showInputDialog("请输入你的姓名:");
String info = hs+"*"+ls+"拼图记录:"+name+"的步数是:"+times+"\r\n";
JOptionPane.showMessageDialog(null, hs+"*"+ls+"拼图记录:"+name+"的步数是:"+times+"\r\n");
try {
FileWriter fw = new FileWriter("D:\\游戏记录.dat",true);
fw.write(info);
fw.close();
}catch (IOException e1) {
e1.printStackTrace();
}
}
}
//判断是否拼图成功
public boolean isWin() {
//获取每一个按钮的坐标
for(int i=0;i<jbs.length;i++){
//jbs[i].setLocation((i%ls)*widthbut, i/ls*heightbut);由之前坐标设置给出下面的x,y
int x=jbs[i].getX()/widthbut;
int y=jbs[i].getY()/heightbut;
//判断,通过下标值,也可以获取按钮的坐标 横坐标 i%ls 纵坐标 i/ls
if (i%ls!=x || i/ls!=y ){
return false;
}
}
return true;
}
}
游戏功能部分
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import javax.swing.ButtonGroup;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.filechooser.FileNameExtensionFilter;
public class MainJFrame extends JFrame implements Runnable{
//菜单
//菜单栏
JMenuBar jmenubar;
//菜单 菜单、等级、帮助
JMenu menu,menuclass,menuhelp;
//菜单项 开始、退出、图片更换、关于游戏、游戏记录、清空记录
JMenuItem itembegin,itemend,itemchange,itemabout,itemrecord,itemclear;
//单选菜单项 简单、一般、困难
JRadioButtonMenuItem itemeasy,itemnormal,itemhard;
//中间面板
JPanel jp;
//左面板
LeftJPanel lp;
//右面板
RightJPanel rp;
//访问的图片
URL url;
//显示计时标签
JLabel total_time;
//起止时间
long startTime,endTime;
//创建线程对象,实现计时功能
Thread th;
//显示步数的标签
JLabel total_count;
//构造方法
public MainJFrame(){
//标题设置
setTitle("拼图游戏");
//窗体大小
setSize(1440, 780);
//窗体位置在容器/屏幕的正中间
setLocationRelativeTo(null);
//窗体大小不可变
setResizable(false);
//实现界面菜单初始化
//创建一个线程对象
th=new Thread(this);
//界面菜单初始化
menuinit();
//各面板的初始化
init();
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
//开始菜单
itembegin.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//启动线程
//如果线程没有启动,则调用start方法启动
if(!th.isAlive()) th.start();
startTime=System.currentTimeMillis();
rp.times=0;
rp.randomOrder();
}
});
//结束游戏
itemend.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.exit(1);
}
});
//选择难易度itemeasy,itemnormal,itemhard
itemeasy.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//第一,传递2*2到右面板
rp.hs=2;
rp.ls=2;
//第二,调用右面板组件初始化的方法
rp.init(url);
}
});
itemnormal.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//第一,传递3*3到右面板
rp.hs=3;
rp.ls=3;
//第二,调用右面板组件初始化的方法
rp.init(url);
}
});
itemhard.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//第一,传递4*4到右面板
rp.hs=4;
rp.ls=4;
//第二,调用右面板组件初始化的方法
rp.init(url);
}
});
//游戏记录显示
itemrecord.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//info存储要显示的内容
String info="";
try {
//判断文件是否存在
File f = new File("D:\\游戏记录.dat");
if(f.exists()) {
//创建指向***的文件字符输入流对象
FileReader fr = new FileReader("D:\\游戏记录.dat");
//读取数据
char[] chs = new char[1024];
int len;
while((len=fr.read(chs))!=-1) {
//读取的结果放在info中
info+=new String(chs,0,len);
}
fr.close();
//通过消息框显示结果
JOptionPane.showMessageDialog(null, info);
}else {
JOptionPane.showMessageDialog(null, "游戏记录为空!");
}
}catch (IOException e1) {
e1.printStackTrace();
}
}
});
//关于游戏
itemabout.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(null, "关于拼图游戏\r\n版本:v2.0\r\n作者:LWL\r\n欢迎进入游戏!");
}
});
//清空记录
itemclear.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
File f = new File("D:\\游戏记录.dat");
if(f.exists()) {
f.delete();
}
}
});
//实现图片的更换
itemchange.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//显示一个打开对话框,选择一个图片文件,将文件转换成url对象,调用左右面板的相应方法
JFileChooser jfc=new JFileChooser();
//设置文件的扩展名
jfc.setFileFilter(new FileNameExtensionFilter("图片格式(jpg|png|gif|jpeg)", "jpg","png","gif","jpeg"));
//弹出打开对话框
int sd = jfc.showOpenDialog(MainJFrame.this);
if (sd==jfc.APPROVE_OPTION)//如果用户选择了打开按钮
{
//获取用户选择的文件完整名称
String file=jfc.getSelectedFile().getAbsolutePath();
try {
url=new URL("file:\\"+file);
//更新两个面板的图片
lp.init(url);
rp.init(url);
} catch (MalformedURLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
});
}
public void init() {
jp=new JPanel();
//设置中间面板的布局方式
jp.setLayout(new GridLayout(1,2));
//提供左右面板的图片
url=this.getClass().getResource("小狗.jpg");
//创建左面板
lp=new LeftJPanel();
//对标签初始化
lp.init(url);
//将左面板添加到中间面板
jp.add(lp);
//创建右面板
rp=new RightJPanel();
//右面板的按钮初始化
rp.init(url);
//将右面板添加到中间面板
jp.add(rp);
//将中间面板添加到窗体
add(jp);
}
public void menuinit() {
jmenubar=new JMenuBar();
menu=new JMenu("菜单");
menuclass=new JMenu("等级");
menuhelp=new JMenu("帮助");
itembegin=new JMenuItem("开始游戏");
itemend=new JMenuItem("结束游戏");
itemchange=new JMenuItem("更换图片");
itemabout=new JMenuItem("关于游戏");
itemrecord=new JMenuItem("游戏记录");
itemclear=new JMenuItem("清空记录");
itemeasy=new JRadioButtonMenuItem("简单");
itemnormal=new JRadioButtonMenuItem("一般");
itemhard=new JRadioButtonMenuItem("困难");
//为单选菜单分组,实现多选一
ButtonGroup bg=new ButtonGroup();
bg.add(itemeasy);
bg.add(itemnormal);
bg.add(itemhard);
//添加菜单
menu.add(itembegin);
menu.add(itemend);
menu.add(itemchange);
menuclass.add(itemeasy);
menuclass.add(itemnormal);
menuclass.add(itemhard);
menuhelp.add(itemabout);
menuhelp.add(itemrecord);
menuhelp.add(itemclear);
jmenubar.add(menu);
jmenubar.add(menuclass);
jmenubar.add(menuhelp);
//菜单栏添加到窗体
this.setJMenuBar(jmenubar);
itemeasy.setSelected(true);
//创建一个线程对象
th=new Thread(this);
total_time=new JLabel("用时:");
total_time.setForeground(Color.red);
jmenubar.add(new JLabel(" "));
jmenubar.add(total_time);
total_count=new JLabel("步数:");
total_count.setForeground(Color.red);
jmenubar.add(new JLabel(" "));
jmenubar.add(total_count);
}
public static void main(String[] args) {
new MainJFrame();
}
//实现计时并定时显示的run()方法
@Override
public void run() {
while(true) {
endTime=System.currentTimeMillis();
total_time.setText("用时:"+(endTime-startTime)/1000+"秒");
total_count.setText("步数:第"+rp.times+"步");
try {
Thread.sleep(500);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}