原文: https://zetcode.com/tutorials/javagamestutorial/puzzle/

在 Java 游戏教程的这一部分中,我们创建一个 Java 解谜游戏克隆。 源代码和图像可以在作者的 Github Java Swing 益智游戏存储库中找到。

Java 益智游戏要点

  • 使用 Swing 和 Java 2D 图形来构建游戏。
  • Collections.shuffle()随机打乱播放按钮。
  • ImageIO.read()加载图像。
  • BufferedImage调整图像大小。
  • CropImageFilter裁剪图像。
  • GridLayout布局按钮。
  • 使用两个点的ArrayLists检查解决方案。

Java 益智游戏示例

这个小游戏的目的是形成一张图片。 通过单击包含图像的按钮来移动它们。 只能移动与空按钮相邻的按钮。

PuzzleEx.java

  1. package com.zetcode;
  2. import java.awt.BorderLayout;
  3. import java.awt.Color;
  4. import java.awt.EventQueue;
  5. import java.awt.Graphics2D;
  6. import java.awt.GridLayout;
  7. import java.awt.Image;
  8. import java.awt.Point;
  9. import java.awt.event.ActionEvent;
  10. import java.awt.event.MouseAdapter;
  11. import java.awt.event.MouseEvent;
  12. import java.awt.image.BufferedImage;
  13. import java.awt.image.CropImageFilter;
  14. import java.awt.image.FilteredImageSource;
  15. import java.io.File;
  16. import java.io.IOException;
  17. import java.util.ArrayList;
  18. import java.util.Collections;
  19. import java.util.List;
  20. import java.util.logging.Level;
  21. import java.util.logging.Logger;
  22. import javax.imageio.ImageIO;
  23. import javax.swing.AbstractAction;
  24. import javax.swing.BorderFactory;
  25. import javax.swing.ImageIcon;
  26. import javax.swing.JButton;
  27. import javax.swing.JComponent;
  28. import javax.swing.JFrame;
  29. import javax.swing.JOptionPane;
  30. import javax.swing.JPanel;
  31. class MyButton extends JButton {
  32. private boolean isLastButton;
  33. public MyButton() {
  34. super();
  35. initUI();
  36. }
  37. public MyButton(Image image) {
  38. super(new ImageIcon(image));
  39. initUI();
  40. }
  41. private void initUI() {
  42. isLastButton = false;
  43. BorderFactory.createLineBorder(Color.gray);
  44. addMouseListener(new MouseAdapter() {
  45. @Override
  46. public void mouseEntered(MouseEvent e) {
  47. setBorder(BorderFactory.createLineBorder(Color.yellow));
  48. }
  49. @Override
  50. public void mouseExited(MouseEvent e) {
  51. setBorder(BorderFactory.createLineBorder(Color.gray));
  52. }
  53. });
  54. }
  55. public void setLastButton() {
  56. isLastButton = true;
  57. }
  58. public boolean isLastButton() {
  59. return isLastButton;
  60. }
  61. }
  62. public class PuzzleEx extends JFrame {
  63. private JPanel panel;
  64. private BufferedImage source;
  65. private BufferedImage resized;
  66. private Image image;
  67. private MyButton lastButton;
  68. private int width, height;
  69. private List<MyButton> buttons;
  70. private List<Point> solution;
  71. private final int NUMBER_OF_BUTTONS = 12;
  72. private final int DESIRED_WIDTH = 300;
  73. public PuzzleEx() {
  74. initUI();
  75. }
  76. private void initUI() {
  77. solution = new ArrayList<>();
  78. solution.add(new Point(0, 0));
  79. solution.add(new Point(0, 1));
  80. solution.add(new Point(0, 2));
  81. solution.add(new Point(1, 0));
  82. solution.add(new Point(1, 1));
  83. solution.add(new Point(1, 2));
  84. solution.add(new Point(2, 0));
  85. solution.add(new Point(2, 1));
  86. solution.add(new Point(2, 2));
  87. solution.add(new Point(3, 0));
  88. solution.add(new Point(3, 1));
  89. solution.add(new Point(3, 2));
  90. buttons = new ArrayList<>();
  91. panel = new JPanel();
  92. panel.setBorder(BorderFactory.createLineBorder(Color.gray));
  93. panel.setLayout(new GridLayout(4, 3, 0, 0));
  94. try {
  95. source = loadImage();
  96. int h = getNewHeight(source.getWidth(), source.getHeight());
  97. resized = resizeImage(source, DESIRED_WIDTH, h,
  98. BufferedImage.TYPE_INT_ARGB);
  99. } catch (IOException ex) {
  100. Logger.getLogger(PuzzleEx.class.getName()).log(
  101. Level.SEVERE, null, ex);
  102. }
  103. width = resized.getWidth(null);
  104. height = resized.getHeight(null);
  105. add(panel, BorderLayout.CENTER);
  106. for (int i = 0; i < 4; i++) {
  107. for (int j = 0; j < 3; j++) {
  108. image = createImage(new FilteredImageSource(resized.getSource(),
  109. new CropImageFilter(j * width / 3, i * height / 4,
  110. (width / 3), height / 4)));
  111. MyButton button = new MyButton(image);
  112. button.putClientProperty("position", new Point(i, j));
  113. if (i == 3 && j == 2) {
  114. lastButton = new MyButton();
  115. lastButton.setBorderPainted(false);
  116. lastButton.setContentAreaFilled(false);
  117. lastButton.setLastButton();
  118. lastButton.putClientProperty("position", new Point(i, j));
  119. } else {
  120. buttons.add(button);
  121. }
  122. }
  123. }
  124. Collections.shuffle(buttons);
  125. buttons.add(lastButton);
  126. for (int i = 0; i < NUMBER_OF_BUTTONS; i++) {
  127. MyButton btn = buttons.get(i);
  128. panel.add(btn);
  129. btn.setBorder(BorderFactory.createLineBorder(Color.gray));
  130. btn.addActionListener(new ClickAction());
  131. }
  132. pack();
  133. setTitle("Puzzle");
  134. setResizable(false);
  135. setLocationRelativeTo(null);
  136. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  137. }
  138. private int getNewHeight(int w, int h) {
  139. double ratio = DESIRED_WIDTH / (double) w;
  140. int newHeight = (int) (h * ratio);
  141. return newHeight;
  142. }
  143. private BufferedImage loadImage() throws IOException {
  144. BufferedImage bimg = ImageIO.read(new File("src/resources/icesid.jpg"));
  145. return bimg;
  146. }
  147. private BufferedImage resizeImage(BufferedImage originalImage, int width,
  148. int height, int type) throws IOException {
  149. BufferedImage resizedImage = new BufferedImage(width, height, type);
  150. Graphics2D g = resizedImage.createGraphics();
  151. g.drawImage(originalImage, 0, 0, width, height, null);
  152. g.dispose();
  153. return resizedImage;
  154. }
  155. private class ClickAction extends AbstractAction {
  156. @Override
  157. public void actionPerformed(ActionEvent e) {
  158. checkButton(e);
  159. checkSolution();
  160. }
  161. private void checkButton(ActionEvent e) {
  162. int lidx = 0;
  163. for (MyButton button : buttons) {
  164. if (button.isLastButton()) {
  165. lidx = buttons.indexOf(button);
  166. }
  167. }
  168. JButton button = (JButton) e.getSource();
  169. int bidx = buttons.indexOf(button);
  170. if ((bidx - 1 == lidx) || (bidx + 1 == lidx)
  171. || (bidx - 3 == lidx) || (bidx + 3 == lidx)) {
  172. Collections.swap(buttons, bidx, lidx);
  173. updateButtons();
  174. }
  175. }
  176. private void updateButtons() {
  177. panel.removeAll();
  178. for (JComponent btn : buttons) {
  179. panel.add(btn);
  180. }
  181. panel.validate();
  182. }
  183. }
  184. private void checkSolution() {
  185. List<Point> current = new ArrayList<>();
  186. for (JComponent btn : buttons) {
  187. current.add((Point) btn.getClientProperty("position"));
  188. }
  189. if (compareList(solution, current)) {
  190. JOptionPane.showMessageDialog(panel, "Finished",
  191. "Congratulation", JOptionPane.INFORMATION_MESSAGE);
  192. }
  193. }
  194. public static boolean compareList(List ls1, List ls2) {
  195. return ls1.toString().contentEquals(ls2.toString());
  196. }
  197. public static void main(String[] args) {
  198. EventQueue.invokeLater(new Runnable() {
  199. @Override
  200. public void run() {
  201. PuzzleEx puzzle = new PuzzleEx();
  202. puzzle.setVisible(true);
  203. }
  204. });
  205. }
  206. }

我们使用的是冰河世纪电影中 Sid 角色的图像。 我们缩放图像并将其切成十二块。 这些片段由JButton组件使用。 最后一块没有使用; 我们有一个空按钮。 您可以下载一些相当大的图片并在游戏中使用它。

  1. addMouseListener(new MouseAdapter() {
  2. @Override
  3. public void mouseEntered(MouseEvent e) {
  4. setBorder(BorderFactory.createLineBorder(Color.yellow));
  5. }
  6. @Override
  7. public void mouseExited(MouseEvent e) {
  8. setBorder(BorderFactory.createLineBorder(Color.gray));
  9. }
  10. });

当我们将鼠标指针悬停在按钮上时,其边框变为黄色。

  1. public boolean isLastButton() {
  2. return isLastButton;
  3. }

有一个按钮称为最后一个按钮。 它是没有图像的按钮。 其他按钮与此空间交换空间。

  1. private final int DESIRED_WIDTH = 300;

我们用来形成的图像被缩放以具有所需的宽度。 使用getNewHeight()方法,我们可以计算新的高度,并保持图像的比例。

  1. solution.add(new Point(0, 0));
  2. solution.add(new Point(0, 1));
  3. solution.add(new Point(0, 2));
  4. solution.add(new Point(1, 0));
  5. ...

解决方案数组列表存储形成图像的按钮的正确顺序。 每个按钮由一个Point标识。

  1. panel.setLayout(new GridLayout(4, 3, 0, 0));

我们使用GridLayout存储我们的组件。 布局由 4 行和 3 列组成。

  1. image = createImage(new FilteredImageSource(resized.getSource(),
  2. new CropImageFilter(j * width / 3, i * height / 4,
  3. (width / 3), height / 4)));

CropImageFilter用于从已调整大小的图像源中切出矩形。 它旨在与FilteredImageSource对象结合使用,以生成现有图像的裁剪版本。

  1. button.putClientProperty("position", new Point(i, j));

按钮由其position客户端属性标识。 这是包含按钮在图片中正确的行和列的位置的点。 这些属性用于确定窗口中按钮的顺序是否正确。

  1. if (i == 3 && j == 2) {
  2. lastButton = new MyButton();
  3. lastButton.setBorderPainted(false);
  4. lastButton.setContentAreaFilled(false);
  5. lastButton.setLastButton();
  6. lastButton.putClientProperty("position", new Point(i, j));
  7. } else {
  8. buttons.add(button);
  9. }

没有图像的按钮称为最后一个按钮。 它放置在右下角网格的末端。 该按钮与被单击的相邻按钮交换位置。 我们使用setLastButton()方法设置其isLastButton标志。

  1. Collections.shuffle(buttons);
  2. buttons.add(lastButton);

我们随机重新排列buttons列表的元素。 最后一个按钮,即没有图像的按钮,被插入到列表的末尾。 它不应该被改组,它总是在我们开始益智游戏时结束。

  1. for (int i = 0; i < NUMBER_OF_BUTTONS; i++) {
  2. MyButton btn = buttons.get(i);
  3. panel.add(btn);
  4. btn.setBorder(BorderFactory.createLineBorder(Color.gray));
  5. btn.addActionListener(new ClickAction());
  6. }

buttons列表中的所有组件都放置在面板上。 我们在按钮周围创建一些灰色边框,并添加一个点击动作监听器。

  1. private int getNewHeight(int w, int h) {
  2. double ratio = DESIRED_WIDTH / (double) w;
  3. int newHeight = (int) (h * ratio);
  4. return newHeight;
  5. }

getNewHeight()方法根据所需的宽度计算图像的高度。 图像的比例保持不变。 我们使用这些值缩放图像。

  1. private BufferedImage loadImage() throws IOException {
  2. BufferedImage bimg = ImageIO.read(new File("src/resources/icesid.jpg"));
  3. return bimg;
  4. }

从磁盘加载了 JPG 图像。 ImageIOread()方法返回BufferedImage,这是 Swing 操纵图像的重要类。

  1. private BufferedImage resizeImage(BufferedImage originalImage, int width,
  2. int height, int type) throws IOException {
  3. BufferedImage resizedImage = new BufferedImage(width, height, type);
  4. Graphics2D g = resizedImage.createGraphics();
  5. g.drawImage(originalImage, 0, 0, width, height, null);
  6. g.dispose();
  7. return resizedImage;
  8. }

通过创建具有新大小的新BufferedImage来调整原始图像的大小。 我们将原始图像绘制到此新的缓冲图像中。

  1. private void checkButton(ActionEvent e) {
  2. int lidx = 0;
  3. for (MyButton button : buttons) {
  4. if (button.isLastButton()) {
  5. lidx = buttons.indexOf(button);
  6. }
  7. }
  8. JButton button = (JButton) e.getSource();
  9. int bidx = buttons.indexOf(button);
  10. if ((bidx - 1 == lidx) || (bidx + 1 == lidx)
  11. || (bidx - 3 == lidx) || (bidx + 3 == lidx)) {
  12. Collections.swap(buttons, bidx, lidx);
  13. updateButtons();
  14. }
  15. }

按钮存储在数组列表中。 然后,此列表将映射到面板的网格。 我们得到最后一个按钮和被单击按钮的索引。 如果它们相邻,则使用Collections.swap()进行交换。

  1. private void updateButtons() {
  2. panel.removeAll();
  3. for (JComponent btn : buttons) {
  4. panel.add(btn);
  5. }
  6. panel.validate();
  7. }

updateButtons()方法将列表映射到面板的网格。 首先,使用removeAll()方法删除所有组件。 for循环用于通过buttons列表,将重新排序的按钮添加回面板的布局管理器。 最后,validate()方法实现了新的布局。

  1. private void checkSolution() {
  2. List<Point> current = new ArrayList<>();
  3. for (JComponent btn : buttons) {
  4. current.add((Point) btn.getClientProperty("position"));
  5. }
  6. if (compareList(solution, current)) {
  7. JOptionPane.showMessageDialog(panel, "Finished",
  8. "Congratulation", JOptionPane.INFORMATION_MESSAGE);
  9. }
  10. }

通过将正确排序的按钮的点列表与包含窗口中按钮顺序的当前列表进行比较,来完成解决方案检查。 如果解决方案出现,则会显示一个消息对话框。

Java 益智游戏 - 图1

图:创建图像

这是用 Java 创建的益智游戏。