原文: http://zetcode.com/tutorials/javaswingtutorial/puzzle/

    在本章中,我们将使用 Java Swing 创建一个简单的益智游戏。 来源可从作者的 Github 仓库中获得。

    Tweet

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

    在这个游戏中,我们学习了如何将图像裁剪成多个部分。

    PuzzleEx.java

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

    我们使用的是冰河世纪电影中 Sid 角色的图像。 我们缩放图像并将其切成 12 张。 这些片段由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. var 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. var 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. var resizedImage = new BufferedImage(width, height, type);
    4. var 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. var 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. var current = new ArrayList<Point>();
    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 Swing 中的益智游戏 - 图1

    图:益智游戏

    这是益智游戏。