首页 文章

制作一个强大的,可调整大小的Swing Chess GUI [关闭]

提问于
浏览
22

我如何制作这款可调整大小的Chess GUI?


我们公司的任务是制作国际象棋游戏 . 它需要在Windows,OS X和Linux / Unix机器上运行,我们选择Java来实现这一点,同时保持一个通用的代码库(便于维护和降低成本) .

我的任务是创建GUI . 用户设计团队已经清除了以下规范 . 与客户 .

国际象棋游戏(国际象棋冠军)将大胆调整和简单,它包括:顶部的工具栏,UI组件:新按钮保存按钮恢复按钮辞职按钮用于向玩家提供消息的标签 . 在游戏的左侧,我们需要一个将被保留以供将来使用的区域,它可能包括以下内容:捕获的棋子列表在推广棋子时选择棋子的选择器游戏统计提示等等详细信息仍在与客户和逻辑团队达成和解 . 那么暂时,只需用包含?的标签来标记它?作为文字 . GUI的其余部分将由棋盘本身组成 . 它将拥有:国际象棋棋盘的主要区域 . 如果用户指向棋子,它应该显示带边框的焦点 . 它也应该是键盘可访问的 . 客户将提供多个棋子(各种尺寸,样式和颜色)的精灵表,以允许用户改变游戏的外观 . 棋盘将有标签指示列(从左到右:A,B,C,D,E,F,G和H)和行(从上到下:8,7,6,5,4,3,2) &1) . 国际象棋棋盘和列/行标签将以1px黑色边框为边界,周围有8px填充 . 随着玩家增加游戏的大小,棋盘应该保持正方形,否则填充可用空间 . 国际象棋棋盘背后的背景色应该是赭色,但在下面的模型中,我们将棋盘背后的区域设为绿色,以突出调整大小的行为 .

在游戏开始前,最小尺寸的国际象棋冠军

ChessChamp at minimum size, before a game is started

激活新游戏按钮后,

最小尺寸的国际象棋冠军

Chess Champ at minimum size, after the new game button is activated

国际象棋冠军比最小尺寸更宽

ChessChamp stretched wider than minimum size

国际象棋冠军比最小尺寸拉得更高

Chess Champ stretched taller than minimum size

2 回答

  • 12

    注意事项

    • 棋盘左侧及上方的棋盘由9x9 GridLayout 提供 . 网格布局的第一个单元格是没有文本的标签 .

    • 为简化游戏逻辑,我们维护一个单独的8x8按钮阵列 .

    • 为了允许键盘功能,我们使用棋盘位置的按钮 . 这也提供了内置焦点指示 . 删除按钮的边距以允许它们缩小到图标的大小 . 我们可以在按钮上添加 ActionListener ,它将响应键盘和鼠标事件 .

    • 为了维持一块方板,我们采用了一点技巧 . 国际象棋棋盘被添加到 GridBagLayout 作为唯一没有指定 GridBagContraints 的组件 . 这样它始终居中 . 为了获得所需的调整大小行为,国际象棋棋盘会查询 parent component, 的实际大小并返回一个可以达到的最大大小,同时仍然是正方形且不超过父级宽度或高度的较小大小 .

    • 棋子图像是从Example images for code and mark-up Q&As获得的,而Example images for code and mark-up Q&As又是由'Fill' Unicode characters in labels开发的 .

    使用图像更简单,而填充Unicode字符更通用,也是'lighter' . I.E.支持4种不同颜色的3种不同尺寸的3种不同的棋子样式需要36个单独的精灵表!


    import java.awt.*;
    import java.awt.event.*;
    import java.awt.image.BufferedImage;
    import javax.swing.*;
    import javax.swing.border.*;
    import java.net.URL;
    import javax.imageio.ImageIO;
    
    public class ChessGUI {
    
        private final JPanel gui = new JPanel(new BorderLayout(3, 3));
        private JButton[][] chessBoardSquares = new JButton[8][8];
        private Image[][] chessPieceImages = new Image[2][6];
        private JPanel chessBoard;
        private final JLabel message = new JLabel(
                "Chess Champ is ready to play!");
        private static final String COLS = "ABCDEFGH";
        public static final int QUEEN = 0, KING = 1,
                ROOK = 2, KNIGHT = 3, BISHOP = 4, PAWN = 5;
        public static final int[] STARTING_ROW = {
            ROOK, KNIGHT, BISHOP, KING, QUEEN, BISHOP, KNIGHT, ROOK
        };
        public static final int BLACK = 0, WHITE = 1;
    
        ChessGUI() {
            initializeGui();
        }
    
        public final void initializeGui() {
            // create the images for the chess pieces
            createImages();
    
            // set up the main GUI
            gui.setBorder(new EmptyBorder(5, 5, 5, 5));
            JToolBar tools = new JToolBar();
            tools.setFloatable(false);
            gui.add(tools, BorderLayout.PAGE_START);
            Action newGameAction = new AbstractAction("New") {
    
                @Override
                public void actionPerformed(ActionEvent e) {
                    setupNewGame();
                }
            };
            tools.add(newGameAction);
            tools.add(new JButton("Save")); // TODO - add functionality!
            tools.add(new JButton("Restore")); // TODO - add functionality!
            tools.addSeparator();
            tools.add(new JButton("Resign")); // TODO - add functionality!
            tools.addSeparator();
            tools.add(message);
    
            gui.add(new JLabel("?"), BorderLayout.LINE_START);
    
            chessBoard = new JPanel(new GridLayout(0, 9)) {
    
                /**
                 * Override the preferred size to return the largest it can, in
                 * a square shape.  Must (must, must) be added to a GridBagLayout
                 * as the only component (it uses the parent as a guide to size)
                 * with no GridBagConstaint (so it is centered).
                 */
                @Override
                public final Dimension getPreferredSize() {
                    Dimension d = super.getPreferredSize();
                    Dimension prefSize = null;
                    Component c = getParent();
                    if (c == null) {
                        prefSize = new Dimension(
                                (int)d.getWidth(),(int)d.getHeight());
                    } else if (c!=null &&
                            c.getWidth()>d.getWidth() &&
                            c.getHeight()>d.getHeight()) {
                        prefSize = c.getSize();
                    } else {
                        prefSize = d;
                    }
                    int w = (int) prefSize.getWidth();
                    int h = (int) prefSize.getHeight();
                    // the smaller of the two sizes
                    int s = (w>h ? h : w);
                    return new Dimension(s,s);
                }
            };
            chessBoard.setBorder(new CompoundBorder(
                    new EmptyBorder(8,8,8,8),
                    new LineBorder(Color.BLACK)
                    ));
            // Set the BG to be ochre
            Color ochre = new Color(204,119,34);
            chessBoard.setBackground(ochre);
            JPanel boardConstrain = new JPanel(new GridBagLayout());
            boardConstrain.setBackground(ochre);
            boardConstrain.add(chessBoard);
            gui.add(boardConstrain);
    
            // create the chess board squares
            Insets buttonMargin = new Insets(0, 0, 0, 0);
            for (int ii = 0; ii < chessBoardSquares.length; ii++) {
                for (int jj = 0; jj < chessBoardSquares[ii].length; jj++) {
                    JButton b = new JButton();
                    b.setMargin(buttonMargin);
                    // our chess pieces are 64x64 px in size, so we'll
                    // 'fill this in' using a transparent icon..
                    ImageIcon icon = new ImageIcon(
                            new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB));
                    b.setIcon(icon);
                    if ((jj % 2 == 1 && ii % 2 == 1)
                            //) {
                            || (jj % 2 == 0 && ii % 2 == 0)) {
                        b.setBackground(Color.WHITE);
                    } else {
                        b.setBackground(Color.BLACK);
                    }
                    chessBoardSquares[jj][ii] = b;
                }
            }
    
            /*
             * fill the chess board
             */
            chessBoard.add(new JLabel(""));
            // fill the top row
            for (int ii = 0; ii < 8; ii++) {
                chessBoard.add(
                        new JLabel(COLS.substring(ii, ii + 1),
                        SwingConstants.CENTER));
            }
            // fill the black non-pawn piece row
            for (int ii = 0; ii < 8; ii++) {
                for (int jj = 0; jj < 8; jj++) {
                    switch (jj) {
                        case 0:
                            chessBoard.add(new JLabel("" + (9-(ii + 1)),
                                    SwingConstants.CENTER));
                        default:
                            chessBoard.add(chessBoardSquares[jj][ii]);
                    }
                }
            }
        }
    
        public final JComponent getGui() {
            return gui;
        }
    
        private final void createImages() {
            try {
                URL url = new URL("http://i.stack.imgur.com/memI0.png");
                BufferedImage bi = ImageIO.read(url);
                for (int ii = 0; ii < 2; ii++) {
                    for (int jj = 0; jj < 6; jj++) {
                        chessPieceImages[ii][jj] = bi.getSubimage(
                                jj * 64, ii * 64, 64, 64);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                System.exit(1);
            }
        }
    
        /**
         * Initializes the icons of the initial chess board piece places
         */
        private final void setupNewGame() {
            message.setText("Make your move!");
            // set up the black pieces
            for (int ii = 0; ii < STARTING_ROW.length; ii++) {
                chessBoardSquares[ii][0].setIcon(new ImageIcon(
                        chessPieceImages[BLACK][STARTING_ROW[ii]]));
            }
            for (int ii = 0; ii < STARTING_ROW.length; ii++) {
                chessBoardSquares[ii][1].setIcon(new ImageIcon(
                        chessPieceImages[BLACK][PAWN]));
            }
            // set up the white pieces
            for (int ii = 0; ii < STARTING_ROW.length; ii++) {
                chessBoardSquares[ii][6].setIcon(new ImageIcon(
                        chessPieceImages[WHITE][PAWN]));
            }
            for (int ii = 0; ii < STARTING_ROW.length; ii++) {
                chessBoardSquares[ii][7].setIcon(new ImageIcon(
                        chessPieceImages[WHITE][STARTING_ROW[ii]]));
            }
        }
    
        public static void main(String[] args) {
            Runnable r = new Runnable() {
    
                @Override
                public void run() {
                    ChessGUI cg = new ChessGUI();
    
                    JFrame f = new JFrame("ChessChamp");
                    f.add(cg.getGui());
                    // Ensures JVM closes after frame(s) closed and
                    // all non-daemon threads are finished
                    f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                    // See https://stackoverflow.com/a/7143398/418556 for demo.
                    f.setLocationByPlatform(true);
    
                    // ensures the frame is the minimum size it needs to be
                    // in order display the components within it
                    f.pack();
                    // ensures the minimum size is enforced.
                    f.setMinimumSize(f.getSize());
                    f.setVisible(true);
                }
            };
            // Swing GUIs should be created and updated on the EDT
            // http://docs.oracle.com/javase/tutorial/uiswing/concurrency
            SwingUtilities.invokeLater(r);
        }
    }
    
  • 6

    我注意到在调整大小时你可以在棋盘和右边/底边线边界之间留一个小间隙 . GridLayout会发生这种情况,因为空格并不总是被9整除 .

    您可能正在寻找使用标准JDK的解决方案,但如果您想摆脱这个小差距,那么您可以使用Relative Layout来管理棋盘和标签 . 差距仍将存在,但我已将其移至标签,因此您无法轻易看出差异 .

    import java.awt.*;
    import java.awt.event.*;
    import java.awt.image.BufferedImage;
    import javax.swing.*;
    import javax.swing.border.*;
    import java.net.URL;
    import javax.imageio.ImageIO;
    
    public class ChessGUI2 {
    
        private final JPanel gui = new JPanel(new BorderLayout(3, 3));
        private JButton[][] chessBoardSquares = new JButton[8][8];
        private Image[][] chessPieceImages = new Image[2][6];
        private JPanel chessBoard;
        private final JLabel message = new JLabel(
                "Chess Champ is ready to play!");
        private static final String COLS = "ABCDEFGH";
        public static final int QUEEN = 0, KING = 1, 
                ROOK = 2, KNIGHT = 3, BISHOP = 4, PAWN = 5;
        public static final int[] STARTING_ROW = {
            ROOK, KNIGHT, BISHOP, KING, QUEEN, BISHOP, KNIGHT, ROOK
        };
    
        ChessGUI2() {
            initializeGui();
        }
    
        public final void initializeGui() {
            // create the images for the chess pieces
            createImages();
    
            // set up the main GUI
            gui.setBorder(new EmptyBorder(5, 5, 5, 5));
            JToolBar tools = new JToolBar();
            tools.setFloatable(false);
            gui.add(tools, BorderLayout.PAGE_START);
            Action newGameAction = new AbstractAction("New") {
    
                @Override
                public void actionPerformed(ActionEvent e) {
                    setupNewGame();
                }
            };
            tools.add(newGameAction);
            tools.add(new JButton("Save")); // TODO - add functionality!
            tools.add(new JButton("Restore")); // TODO - add functionality!
            tools.addSeparator();
            tools.add(new JButton("Resign")); // TODO - add functionality!
            tools.addSeparator();
            tools.add(message);
    
            gui.add(new JLabel("?"), BorderLayout.LINE_START);
    
    //        chessBoard = new JPanel(new GridLayout(0, 9)) {
            chessBoard = new JPanel() {
    
                /**
                 * Override the preferred size to return the largest it can, in
                 * a square shape.  Must (must, must) be added to a GridBagLayout
                 * as the only component (it uses the parent as a guide to size)
                 * with no GridBagConstaint (so it is centered).
                 */
                @Override
                public final Dimension getPreferredSize() {
                    Dimension d = super.getPreferredSize();
                    Dimension prefSize = null;
                    Component c = getParent();
                    if (c == null) {
                        prefSize = new Dimension(
                                (int)d.getWidth(),(int)d.getHeight());
                    } else if (c!=null &&
                            c.getWidth()>d.getWidth() &&
                            c.getHeight()>d.getHeight()) {
                        prefSize = c.getSize();
                    } else {
                        prefSize = d;
                    }
                    int w = (int) prefSize.getWidth();
                    int h = (int) prefSize.getHeight();
                    // the smaller of the two sizes
                    int s = (w>h ? h : w);
                    return new Dimension(s,s);
                }
            };
    
            RelativeLayout rl = new RelativeLayout(RelativeLayout.Y_AXIS);
            rl.setRoundingPolicy( RelativeLayout.FIRST );
            rl.setFill(true);
            chessBoard.setLayout( rl );
    
            chessBoard.setBorder(new CompoundBorder(
                    new EmptyBorder(8,8,8,8),
                    new LineBorder(Color.BLACK)
                    ));
            // Set the BG to be ochre
            Color ochre = new Color(204,119,34);
            chessBoard.setBackground(ochre);
            JPanel boardConstrain = new JPanel(new GridBagLayout());
            boardConstrain.setBackground(ochre);
            boardConstrain.add(chessBoard);
            gui.add(boardConstrain);
    
    
            // our chess pieces are 64x64 px in size, so we'll
            // 'fill this in' using a transparent icon..
            ImageIcon icon = new ImageIcon(
                    //new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB));
                    new BufferedImage(48, 48, BufferedImage.TYPE_INT_ARGB));
    
            // create the chess board squares
            Insets buttonMargin = new Insets(0, 0, 0, 0);
            for (int ii = 0; ii < chessBoardSquares.length; ii++) {
                for (int jj = 0; jj < chessBoardSquares[ii].length; jj++) {
                    JButton b = new JButton();
                    b.setMargin(buttonMargin);
                    b.setIcon(icon);
                    if ((jj % 2 == 1 && ii % 2 == 1)
                            //) {
                            || (jj % 2 == 0 && ii % 2 == 0)) {
                        b.setBackground(Color.WHITE);
                    } else {
                        b.setBackground(Color.BLACK);
                    }
                    chessBoardSquares[jj][ii] = b;
                }
            }
    
            /*
             * fill the chess board
             */
    
            RelativeLayout topRL = new RelativeLayout(RelativeLayout.X_AXIS);
            topRL.setRoundingPolicy( RelativeLayout.FIRST );
            topRL.setFill(true);
            JPanel top = new JPanel( topRL );
            top.setOpaque(false);
            chessBoard.add(top, new Float(1));
    
            top.add(new JLabel(""), new Float(1));
    
            // fill the top row
            for (int ii = 0; ii < 8; ii++) {
                JLabel label = new JLabel(COLS.substring(ii, ii + 1), SwingConstants.CENTER);
                top.add(label, new Float(1));
            }
            // fill the black non-pawn piece row
            for (int ii = 0; ii < 8; ii++) {
    
                RelativeLayout rowRL = new RelativeLayout(RelativeLayout.X_AXIS);
                rowRL.setRoundingPolicy( RelativeLayout.FIRST );
                rowRL.setFill(true);
                JPanel row = new JPanel( rowRL );
                row.setOpaque(false);
                chessBoard.add(row, new Float(1));
    
                for (int jj = 0; jj < 8; jj++) {
                    switch (jj) {
                        case 0:
                            row.add(new JLabel("" + (9-(ii + 1)), SwingConstants.CENTER), new Float(1));
                        default:
                            row.add(chessBoardSquares[jj][ii], new Float(1));
                    }
                }
            }
        }
    
        public final JComponent getChessBoard() {
            return chessBoard;
        }
    
        public final JComponent getGui() {
            return gui;
        }
    
        private final void createImages() {
            try {
                URL url = new URL("http://i.stack.imgur.com/memI0.png");
                BufferedImage bi = ImageIO.read(url);
                for (int ii = 0; ii < 2; ii++) {
                    for (int jj = 0; jj < 6; jj++) {
                        chessPieceImages[ii][jj] = bi.getSubimage(
    //                            jj * 64, ii * 64, 64, 64);
                                jj * 64, ii * 64, 48, 48);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                System.exit(1);
            }
        }
    
        /**
         * Initializes the icons of the initial chess board piece places
         */
        private final void setupNewGame() {
            message.setText("Make your move!");
            // set up the black pieces
            for (int ii = 0; ii < STARTING_ROW.length; ii++) {
                chessBoardSquares[ii][0].setIcon(new ImageIcon(
                        chessPieceImages[0][STARTING_ROW[ii]]));
            }
            for (int ii = 0; ii < STARTING_ROW.length; ii++) {
                chessBoardSquares[ii][1].setIcon(new ImageIcon(
                        chessPieceImages[0][PAWN]));
            }
            // set up the white pieces
            for (int ii = 0; ii < STARTING_ROW.length; ii++) {
                chessBoardSquares[ii][6].setIcon(new ImageIcon(
                        chessPieceImages[1][PAWN]));
            }
            for (int ii = 0; ii < STARTING_ROW.length; ii++) {
                chessBoardSquares[ii][7].setIcon(new ImageIcon(
                        chessPieceImages[1][STARTING_ROW[ii]]));
            }
        }
    
        public static void main(String[] args) {
            Runnable r = new Runnable() {
    
                @Override
                public void run() {
                    ChessGUI2 cg = new ChessGUI2();
    
                    JFrame f = new JFrame("ChessChamp");
                    f.add(cg.getGui());
                    // Ensures JVM closes after frame(s) closed and
                    // all non-daemon threads are finished
                    f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                    // See http://stackoverflow.com/a/7143398/418556 for demo.
                    f.setLocationByPlatform(true);
    
                    // ensures the frame is the minimum size it needs to be
                    // in order display the components within it
                    f.pack();
                    // ensures the minimum size is enforced.
                    f.setMinimumSize(f.getSize());
                    f.setVisible(true);
                }
            };
            // Swing GUIs should be created and updated on the EDT
            // http://docs.oracle.com/javase/tutorial/uiswing/concurrency
            SwingUtilities.invokeLater(r);
        }
    }
    

    它确实需要更多工作,因为您需要单独管理行,而不是在网格中 . 此外,我更改了使用48x48图像的代码,以便在我的小型显示器上更轻松地调整测试大小 .

相关问题