1 实训基本信息
1.1 实训项目名称
(1)拼图游戏
1.2 实训环境
本次实训内容主要针对专业学生,实训形式以实战讲解为主导。实训课程由实训老师主讲,实训老师教学严谨又平易近人,讲解的内容非常细致和认真,对于重要的知识点内容老师还特意的标注下来,方便同学们回顾学习。
2 实训内容简介
2.1 拼图游戏
拼图游戏内容由若干小图像块组成的,其中有一个空白的小图像块,该图像块是来与别的图像块交换的,以此来实现大图像的拼凑。在Java标准环境下运行,通过鼠标点击图像块上下左右移动,完成大图像的拼凑。
通过所创建的窗体类、菜单、中间面板和左右面板完成设计拼图的交互界面 ,实现游戏登录、开始游戏、退出游戏、选择图片、图片缩放、图片分割、调整难易度、计数计时、打乱图片、判断拼图成功等功能。
游戏可以通过对图片文件的读取和加载,从多张图片中选择来进行拼图游戏,通过设置图像块的个数来实现对游戏难易度的调整,最后将游戏完成后所用时间和步数记录到游戏记录中,方便用户查看游戏记录。
3 项目开发过程
3.1 拼图游戏
3.1.1开发步骤
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完成读取数据。
4 游戏截图
5 程序源代码
登录界面部分(用户名:admin 密码:123)
1. import java.awt.Color; 2. import java.awt.Font; 3. import java.awt.GridLayout; 4. import java.awt.event.ActionEvent; 5. import java.awt.event.ActionListener; 6. 7. import javax.swing.JButton; 8. import javax.swing.JFrame; 9. import javax.swing.JLabel; 10. import javax.swing.JOptionPane; 11. import javax.swing.JPasswordField; 12. import javax.swing.JTextField; 13. 14. public class LoginPintu extends JFrame{ 15. JLabel jl1,jl2,jl3,jl4; 16. JTextField jtf;//文本框 17. JPasswordField jpf;//密码 18. JButton jb1,jb2; 19. public LoginPintu() { 20. this.setTitle("拼图游戏"); 21. setBounds(400,350,500,400); 22. //设置窗体为流式布局 23. setLayout(new GridLayout(20,1)); 24. //空布局 25. setLayout(null); 26. init(); 27. setVisible(true); 28. setDefaultCloseOperation(EXIT_ON_CLOSE); 29. jb1.addActionListener(new ActionListener(){ 30. public void actionPerformed(ActionEvent e) { 31. if(jtf.getText().trim().equals("admin")&& 32. new String(jpf.getPassword()).trim().equals("123")) 33. {JOptionPane.showMessageDialog(null, "欢迎进入游戏!"); 34. new MainJFrame();} 35. else if(jtf.getText().trim().length()==0|| 36. new String(jpf.getPassword()).trim().length()==0) 37. {JOptionPane.showMessageDialog(null, "用户名或密码不能为空!");} 38. else {JOptionPane.showMessageDialog(null, "用户名或密码错误!");} 39. } 40. }); 41. jb2.addActionListener(new ActionListener() { 42. public void actionPerformed(ActionEvent e) { 43. // System.exit(0); 44. //获取事件源对象 45. JButton jb=(JButton)e.getSource(); 46. jtf.setText(jb.getText()); 47. } 48. }); 49. 50. } 51. public void init() { 52. jl1=new JLabel("拼图游戏登录窗口"); 53. jl2=new JLabel("用户名:"); 54. jl3=new JLabel("密码:"); 55. jtf=new JTextField(10); 56. jpf=new JPasswordField(10); 57. jb1=new JButton("登录"); 58. jb2=new JButton("取消"); 59. jl1.setBounds(150,30,200,60); 60. jl2.setBounds(100, 120, 180, 30); 61. jtf.setBounds(200, 120, 180, 30); 62. jl3.setBounds(100, 180, 180, 30); 63. jpf.setBounds(200, 180, 180, 30); 64. jb1.setBounds(100, 260, 100, 30); 65. jb2.setBounds(220, 260, 100, 30); 66. Font font = new Font("楷体",Font.PLAIN,25); 67. jl1.setFont(font); 68. jl1.setForeground(Color.red); 69. add(jl1); 70. add(jl2); 71. add(jtf); 72. add(jl3); 73. add(jpf); 74. add(jb1); 75. add(jb2); 76. } 77. public static void main(String[] args) { 78. new LoginPintu(); 79. } 80. }
左面板部分
1. import java.awt.Image; 2. import java.net.MalformedURLException; 3. import java.net.URL; 4. 5. import javax.swing.ImageIcon; 6. import javax.swing.JLabel; 7. import javax.swing.JPanel; 8. //左面板类 9. public class LeftJPanel extends JPanel { 10. JLabel jl; 11. int width=700; 12. int height=700; 13. 14. //构造方法 15. //标签创建,指定图片,放置到面板中 16. public LeftJPanel(){ 17. //左面板大小 18. setSize(width,height); 19. jl=new JLabel(); 20. jl.setSize(width,height); 21. //把标签添加到面板中 22. this.add(jl); 23. } 24. public void init(URL url){ 25. 26. 27. //绝对路径:访问文件是从盘符开始 28. // ImageIcon icon=new ImageIcon("D:\\1picture\\s4.jpg"); 29. //相对路径:访问路径不是从盘符开始,可以是\,也可以是一个文件夹 30. // ImageIcon icon=new ImageIcon("s4.jpg");//参数是字符串的相对路径,相对于当前项目根目录 31. //相对路径下url的获取 32. 33. // //绝对路径的url的获取 34. // URL url=null; 35. // try { 36. // url = new URL("file:\\D:\\1picture\\5.jpg"); 37. // } catch (MalformedURLException e) { 38. // // TODO Auto-generated catch block 39. // e.printStackTrace(); 40. // } 41. ImageIcon icon=new ImageIcon(url); 42. //方法一:图片缩放 43. // Image image = icon.getImage(); 44. // Image image2 = image.getScaledInstance(700, 700, 1); 45. // ImageIcon icon2 = new ImageIcon(image2); 46. // jl.setIcon(icon2); 47. //链式编程方式实现图片缩放 48. jl.setIcon(new ImageIcon(icon.getImage().getScaledInstance(width, height, 1))); 49. //刷新界面 50. validate(); 51. } 52. }
右面板部分
1. import java.awt.Image; 2. import java.awt.event.ActionEvent; 3. import java.awt.event.ActionListener; 4. import java.awt.image.BufferedImage; 5. import java.io.FileInputStream; 6. import java.io.FileWriter; 7. import java.io.IOException; 8. import java.net.URL; 9. import java.util.Random; 10. 11. import javax.imageio.ImageIO; 12. import javax.swing.ImageIcon; 13. import javax.swing.JButton; 14. import javax.swing.JOptionPane; 15. import javax.swing.JPanel; 16. 17. import jdk.jfr.events.FileWriteEvent; 18. //右面板实现ActionListener接口,右面板也就成为了监听器 19. public class RightJPanel extends JPanel implements ActionListener{ 20. 21. //面板的大小 22. int width=700; 23. int height=700; 24. //定义按钮数组 25. JButton[] jbs; 26. //设置分割的行列数 27. int hs=2,ls=2; 28. //按钮的宽度和高度,指定是小图图片缩放的尺寸 29. int widthbut,heightbut; 30. 31. //图片原始高度宽度 32. int widthtp,heighttp; 33. 34. //小图的原始宽度高度 35. int widthxt,heightxt; 36. 37. //实现步数计算的变量 38. int times; 39. 40. //空白按钮 41. JButton kb; 42. public RightJPanel(){ 43. //面板布局是空布局 44. setLayout(null); 45. setSize(width,height); 46. //init(); 47. 48. } 49. //创建按钮,并放置到右面板 50. public void init(URL url) { 51. //面板组件初始化前,先清除所有已有的组件 52. this.removeAll(); 53. //创建按钮数组 54. jbs=new JButton[hs*ls]; 55. //为每一个按钮实现初始化 56. //计算按钮的宽度和高度 57. //面板是700*700,拆分成3*3的9个区域 58. //每一块区域的宽度 700/3 59. //每一块区域的高度 700/3 60. widthbut=width/ls; 61. heightbut=height/hs; 62. 63. BufferedImage buf=null; 64. try { 65. buf = ImageIO.read(url); 66. //获取原图的宽度、高度 67. widthtp=buf.getWidth(); 68. heighttp=buf.getHeight(); 69. //获取小图的宽度和高度 70. widthxt=widthtp/ls; 71. heightxt=heighttp/hs; 72. //每一块按钮的坐标位置确定 73. for(int i=0;i<jbs.length;i++){ 74. jbs[i]=new JButton(); 75. jbs[i].setSize(widthbut,heightbut); 76. //jbs[i].setText(i+""); 77. //添加按钮前要确定坐标位置 78. //横坐标 i=0 0 i=1 233 i=2 466 79. //i=3 0 i=4 233 80. //纵坐标 i=3 81. jbs[i].setLocation((i%ls)*widthbut, i/ls*heightbut); 82. //jbs[i].setIcon(null); 83. //小图的获取 84. BufferedImage subimage = buf.getSubimage(i%ls*widthxt, i/ls*heightxt, widthxt, heightxt); 85. //小图的缩放 86. Image image = subimage.getScaledInstance(widthbut, heightbut, 1); 87. //将小图图片放置到按钮上 88. jbs[i].setIcon(new ImageIcon(image)); 89. //添加按钮到右面板 90. add(jbs[i]); 91. //设置按钮不可用 92. jbs[i].setEnabled(false); 93. //设置按钮的监听,当按钮被单击,会到右面板中找actionPerformed方法执行 94. jbs[i].addActionListener(this); 95. } 96. jbs[hs*ls-1].setIcon(null); 97. kb=jbs[hs*ls-1]; 98. } catch (IOException e) { 99. // TODO Auto-generated catch block 100. e.printStackTrace(); 101. } 102. 103. 104. } 105. 106. //打乱按钮在面板中显示的顺序 107. public void randomOrder(){ 108. //创建随机数对象 109. Random rand=new Random(); 110. //打乱多次 111. for(int i=0;i<hs*ls;i++){ 112. //随机索引 113. int index1=rand.nextInt(hs*ls); 114. int index2=rand.nextInt(hs*ls); 115. int x1=jbs[index1].getX(); 116. int y1=jbs[index1].getY(); 117. int x2=jbs[index2].getX(); 118. int y2=jbs[index2].getY(); 119. jbs[index1].setLocation(x2, y2); 120. jbs[index2].setLocation(x1, y1); 121. jbs[i].setEnabled(true); 122. } 123. } 124. 125. //按钮的单击事件执行的代码 126. @Override 127. public void actionPerformed(ActionEvent e) { 128. // 判断单击按钮和空白按钮是否相邻,如果相邻,则位置互换 129. //获取用户单击的按钮 ,通过ActionEvent e的方法gerSource获取事件源 130. JButton jb=(JButton)(e.getSource()); 131. //获取单击按钮和空白按钮的坐标 132. int x1=jb.getX(); 133. int y1=jb.getY(); 134. int x2=kb.getX(); 135. int y2=kb.getY(); 136. //判断是否可以移动 137. //Math.abs(x1-x2)/widthbut + Math.abs(y1-y2)/heightbut==1 138. if (Math.abs(x1-x2)/widthbut + Math.abs(y1-y2)/heightbut==1){ 139. jb.setLocation(x2, y2); 140. kb.setLocation(x1, y1); 141. times++; 142. } 143. //判断是否拼图成功 144. if (isWin()){ 145. JOptionPane.showMessageDialog(null, "恭喜你,拼图成功"); 146. //使得按钮不可用 147. for(int i=0;i<jbs.length;i++){ 148. jbs[i].setEnabled(false); 149. } 150. //提示用户输入名称 151. //使用输入对话框 152. String name = JOptionPane.showInputDialog("请输入你的姓名:"); 153. String info = hs+"*"+ls+"拼图记录:"+name+"的步数是:"+times+"\r\n"; 154. JOptionPane.showMessageDialog(null, hs+"*"+ls+"拼图记录:"+name+"的步数是:"+times+"\r\n"); 155. try { 156. FileWriter fw = new FileWriter("D:\\游戏记录.dat",true); 157. fw.write(info); 158. fw.close(); 159. }catch (IOException e1) { 160. e1.printStackTrace(); 161. } 162. } 163. 164. } 165. 166. //判断是否拼图成功 167. public boolean isWin() { 168. 169. //获取每一个按钮的坐标 170. for(int i=0;i<jbs.length;i++){ 171. //jbs[i].setLocation((i%ls)*widthbut, i/ls*heightbut);由之前坐标设置给出下面的x,y 172. int x=jbs[i].getX()/widthbut; 173. int y=jbs[i].getY()/heightbut; 174. //判断,通过下标值,也可以获取按钮的坐标 横坐标 i%ls 纵坐标 i/ls 175. if (i%ls!=x || i/ls!=y ){ 176. return false; 177. } 178. } 179. return true; 180. } 181. }
游戏功能部分
1. import java.awt.Color; 2. import java.awt.GridLayout; 3. import java.awt.event.ActionEvent; 4. import java.awt.event.ActionListener; 5. import java.io.File; 6. import java.io.FileReader; 7. import java.io.IOException; 8. import java.net.MalformedURLException; 9. import java.net.URL; 10. 11. import javax.swing.ButtonGroup; 12. import javax.swing.JFileChooser; 13. import javax.swing.JFrame; 14. import javax.swing.JLabel; 15. import javax.swing.JMenu; 16. import javax.swing.JMenuBar; 17. import javax.swing.JMenuItem; 18. import javax.swing.JOptionPane; 19. import javax.swing.JPanel; 20. import javax.swing.JRadioButtonMenuItem; 21. import javax.swing.filechooser.FileNameExtensionFilter; 22. 23. public class MainJFrame extends JFrame implements Runnable{ 24. 25. //菜单 26. //菜单栏 27. JMenuBar jmenubar; 28. //菜单 菜单、等级、帮助 29. JMenu menu,menuclass,menuhelp; 30. //菜单项 开始、退出、图片更换、关于游戏、游戏记录、清空记录 31. JMenuItem itembegin,itemend,itemchange,itemabout,itemrecord,itemclear; 32. //单选菜单项 简单、一般、困难 33. JRadioButtonMenuItem itemeasy,itemnormal,itemhard; 34. //中间面板 35. JPanel jp; 36. //左面板 37. LeftJPanel lp; 38. //右面板 39. RightJPanel rp; 40. //访问的图片 41. URL url; 42. //显示计时标签 43. JLabel total_time; 44. //起止时间 45. long startTime,endTime; 46. //创建线程对象,实现计时功能 47. Thread th; 48. //显示步数的标签 49. JLabel total_count; 50. //构造方法 51. public MainJFrame(){ 52. //标题设置 53. setTitle("拼图游戏"); 54. //窗体大小 55. setSize(1440, 780); 56. //窗体位置在容器/屏幕的正中间 57. setLocationRelativeTo(null); 58. //窗体大小不可变 59. setResizable(false); 60. //实现界面菜单初始化 61. //创建一个线程对象 62. th=new Thread(this); 63. //界面菜单初始化 64. menuinit(); 65. //各面板的初始化 66. init(); 67. setDefaultCloseOperation(EXIT_ON_CLOSE); 68. setVisible(true); 69. //开始菜单 70. itembegin.addActionListener(new ActionListener() { 71. 72. @Override 73. public void actionPerformed(ActionEvent e) { 74. //启动线程 75. //如果线程没有启动,则调用start方法启动 76. if(!th.isAlive()) th.start(); 77. startTime=System.currentTimeMillis(); 78. rp.times=0; 79. rp.randomOrder(); 80. } 81. }); 82. //结束游戏 83. itemend.addActionListener(new ActionListener() { 84. 85. @Override 86. public void actionPerformed(ActionEvent e) { 87. System.exit(1); 88. } 89. }); 90. //选择难易度itemeasy,itemnormal,itemhard 91. itemeasy.addActionListener(new ActionListener() { 92. 93. @Override 94. public void actionPerformed(ActionEvent e) { 95. //第一,传递2*2到右面板 96. rp.hs=2; 97. rp.ls=2; 98. //第二,调用右面板组件初始化的方法 99. rp.init(url); 100. } 101. }); 102. itemnormal.addActionListener(new ActionListener() { 103. 104. @Override 105. public void actionPerformed(ActionEvent e) { 106. //第一,传递3*3到右面板 107. rp.hs=3; 108. rp.ls=3; 109. //第二,调用右面板组件初始化的方法 110. rp.init(url); 111. } 112. }); 113. itemhard.addActionListener(new ActionListener() { 114. 115. @Override 116. public void actionPerformed(ActionEvent e) { 117. //第一,传递4*4到右面板 118. rp.hs=4; 119. rp.ls=4; 120. //第二,调用右面板组件初始化的方法 121. rp.init(url); 122. } 123. }); 124. //游戏记录显示 125. itemrecord.addActionListener(new ActionListener() { 126. 127. @Override 128. public void actionPerformed(ActionEvent e) { 129. //info存储要显示的内容 130. String info=""; 131. try { 132. //判断文件是否存在 133. File f = new File("D:\\游戏记录.dat"); 134. if(f.exists()) { 135. //创建指向***的文件字符输入流对象 136. FileReader fr = new FileReader("D:\\游戏记录.dat"); 137. //读取数据 138. char[] chs = new char[1024]; 139. int len; 140. while((len=fr.read(chs))!=-1) { 141. //读取的结果放在info中 142. info+=new String(chs,0,len); 143. } 144. 145. fr.close(); 146. //通过消息框显示结果 147. JOptionPane.showMessageDialog(null, info); 148. }else { 149. JOptionPane.showMessageDialog(null, "游戏记录为空!"); 150. } 151. }catch (IOException e1) { 152. e1.printStackTrace(); 153. } 154. } 155. }); 156. //关于游戏 157. itemabout.addActionListener(new ActionListener() { 158. 159. @Override 160. public void actionPerformed(ActionEvent e) { 161. JOptionPane.showMessageDialog(null, "关于拼图游戏\r\n版本:v2.0\r\n作者:LWL\r\n欢迎进入游戏!"); 162. } 163. }); 164. //清空记录 165. itemclear.addActionListener(new ActionListener() { 166. 167. @Override 168. public void actionPerformed(ActionEvent e) { 169. File f = new File("D:\\游戏记录.dat"); 170. if(f.exists()) { 171. f.delete(); 172. } 173. } 174. }); 175. //实现图片的更换 176. itemchange.addActionListener(new ActionListener() { 177. 178. @Override 179. public void actionPerformed(ActionEvent e) { 180. //显示一个打开对话框,选择一个图片文件,将文件转换成url对象,调用左右面板的相应方法 181. JFileChooser jfc=new JFileChooser(); 182. //设置文件的扩展名 183. jfc.setFileFilter(new FileNameExtensionFilter("图片格式(jpg|png|gif|jpeg)", "jpg","png","gif","jpeg")); 184. //弹出打开对话框 185. int sd = jfc.showOpenDialog(MainJFrame.this); 186. if (sd==jfc.APPROVE_OPTION)//如果用户选择了打开按钮 187. { 188. //获取用户选择的文件完整名称 189. String file=jfc.getSelectedFile().getAbsolutePath(); 190. try { 191. url=new URL("file:\\"+file); 192. //更新两个面板的图片 193. lp.init(url); 194. rp.init(url); 195. } catch (MalformedURLException e1) { 196. // TODO Auto-generated catch block 197. e1.printStackTrace(); 198. } 199. } 200. } 201. }); 202. } 203. 204. public void init() { 205. jp=new JPanel(); 206. //设置中间面板的布局方式 207. jp.setLayout(new GridLayout(1,2)); 208. //提供左右面板的图片 209. url=this.getClass().getResource("小狗.jpg"); 210. //创建左面板 211. lp=new LeftJPanel(); 212. //对标签初始化 213. lp.init(url); 214. //将左面板添加到中间面板 215. jp.add(lp); 216. //创建右面板 217. rp=new RightJPanel(); 218. //右面板的按钮初始化 219. rp.init(url); 220. //将右面板添加到中间面板 221. jp.add(rp); 222. //将中间面板添加到窗体 223. add(jp); 224. } 225. 226. public void menuinit() { 227. jmenubar=new JMenuBar(); 228. menu=new JMenu("菜单"); 229. menuclass=new JMenu("等级"); 230. menuhelp=new JMenu("帮助"); 231. itembegin=new JMenuItem("开始游戏"); 232. itemend=new JMenuItem("结束游戏"); 233. itemchange=new JMenuItem("更换图片"); 234. itemabout=new JMenuItem("关于游戏"); 235. itemrecord=new JMenuItem("游戏记录"); 236. itemclear=new JMenuItem("清空记录"); 237. itemeasy=new JRadioButtonMenuItem("简单"); 238. itemnormal=new JRadioButtonMenuItem("一般"); 239. itemhard=new JRadioButtonMenuItem("困难"); 240. //为单选菜单分组,实现多选一 241. ButtonGroup bg=new ButtonGroup(); 242. bg.add(itemeasy); 243. bg.add(itemnormal); 244. bg.add(itemhard); 245. //添加菜单 246. menu.add(itembegin); 247. menu.add(itemend); 248. menu.add(itemchange); 249. 250. menuclass.add(itemeasy); 251. menuclass.add(itemnormal); 252. menuclass.add(itemhard); 253. 254. menuhelp.add(itemabout); 255. menuhelp.add(itemrecord); 256. menuhelp.add(itemclear); 257. 258. jmenubar.add(menu); 259. jmenubar.add(menuclass); 260. jmenubar.add(menuhelp); 261. 262. //菜单栏添加到窗体 263. this.setJMenuBar(jmenubar); 264. itemeasy.setSelected(true); 265. //创建一个线程对象 266. th=new Thread(this); 267. total_time=new JLabel("用时:"); 268. total_time.setForeground(Color.red); 269. jmenubar.add(new JLabel(" ")); 270. jmenubar.add(total_time); 271. total_count=new JLabel("步数:"); 272. total_count.setForeground(Color.red); 273. jmenubar.add(new JLabel(" ")); 274. jmenubar.add(total_count); 275. } 276. 277. public static void main(String[] args) { 278. new MainJFrame(); 279. } 280. //实现计时并定时显示的run()方法 281. @Override 282. public void run() { 283. while(true) { 284. endTime=System.currentTimeMillis(); 285. total_time.setText("用时:"+(endTime-startTime)/1000+"秒"); 286. total_count.setText("步数:第"+rp.times+"步"); 287. try { 288. Thread.sleep(500); 289. }catch (InterruptedException e) { 290. e.printStackTrace(); 291. } 292. } 293. } 294. }