首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >简单Java俄罗斯方块游戏

简单Java俄罗斯方块游戏
EN

Code Review用户
提问于 2022-06-18 17:28:55
回答 2查看 332关注 0票数 3

我没有IT背景,自学了Java,制作了一个简单的俄罗斯方块游戏。我把我在互联网上的代码和其他教程进行了比较。我对这些实现的第一印象是它们非常复杂:形状类、无形状形状、旋转数组等等。

形状在网格中的位置也是形状的中心/旋转点。形状是一个相对于形状中央/旋转点的4个点/坐标的数组。

我的代码是不是太简单了?我想要一些反馈。

这是我的代码:

代码语言:javascript
复制
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;

public class Tetris extends JFrame implements Runnable {
    final private static String I = "i";
    final private static String O = "o";
    final private static String J = "j";
    final private static String L = "l";
    final private static String T = "t";
    final private static String S = "s";
    final private static String Z = "z";
    
    final private static HashMap SHAPE = new HashMap();
    final private static HashMap COLOR = new HashMap();
    
    final private int X = 10;
    final private int Y = 20;
    final private int SIZE = Toolkit.getDefaultToolkit().getScreenSize().height / 2 / Y;
    final private int MIN = 100;
    final private int MAX = 750;
    final private int DELAY = 5;
    
    final private JLabel PIECE = new JLabel() {
        @Override
        public void paint(Graphics g) {
            g.setColor((Color) COLOR.get(getText()));
            for (Point p : block) {
                g.fillRect((location.x + p.x) * SIZE, (location.y + p.y) * SIZE, SIZE, SIZE);
            }
        }
    };
    final private JLabel NEXT = new JLabel((String) SHAPE.keySet().toArray()[new Random().nextInt(SHAPE.size())]) {
        @Override
        public void paint(Graphics g) {
            g.setColor((Color) COLOR.get(getText()));
            for (Point p : (Point[]) SHAPE.get(getText())) {
                g.fillRect(getWidth() / 2 + (int) ((p.x - 0.5) * SIZE), (p.y + 2) * SIZE, SIZE, SIZE);
            }
        }
    };
    final private JLabel GAME_OVER = new JLabel("Game over", JLabel.CENTER);
    final private JLabel SCORE = new JLabel("0", JLabel.RIGHT);
    
    final private JButton START = new JButton("Start");
    
    private ArrayList<Color[]> grid = new ArrayList(Arrays.asList(new Color[Y][X]));
    
    private Point location;
    private Point[] block;

    public Tetris() {
        JLayeredPane board = new JLayeredPane();
        JPanel statusPanel = new JPanel(new BorderLayout());
            
        board.setOpaque(true);
        board.setBackground(Color.lightGray);
        board.add(new JPanel() {
            @Override
            public void paint(Graphics g) {
                for (int y = 0; y < Y; y++) {
                    for (int x = 0; x < X; x++) {
                        if (grid.get(y)[x] != null) {
                            g.setColor(grid.get(y)[x]);
                            g.fillRect(x * SIZE, y * SIZE, SIZE, SIZE);
                        }
                    }
                }
            }
        });
        board.add(PIECE, new Integer(1));
        board.add(GAME_OVER, new Integer(2));
        board.setPreferredSize(new Dimension(X * SIZE, Y * SIZE));
        for (Component c : board.getComponents()) {
            c.setSize(board.getPreferredSize());
        }

        statusPanel.add(SCORE, BorderLayout.NORTH);
        statusPanel.add(START, BorderLayout.CENTER);
        statusPanel.add(NEXT, BorderLayout.SOUTH);
        
        PIECE.setVisible(false);
        PIECE.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_LEFT) {
                    blockXY(location.x - 1, location.y, block);
                } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
                    blockXY(location.x + 1, location.y, block);
                } else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
                    blockXY(location.x, location.y + 1, block);
                } else if (e.getKeyCode() == KeyEvent.VK_SPACE && !PIECE.getText().equals(O)) {
                    Point[] rotated = new Point[block.length];
                    int x = location.x;
                    for (int i = 0; i < rotated.length; i++) {
                        rotated[i] = new Point(block[i].y, -block[i].x);
                        x = Math.max(-rotated[i].x, Math.min(x, X - 1 - rotated[i].x));
                    }
                    blockXY(x, location.y, rotated);
                }
            }
        });
        PIECE.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentShown(ComponentEvent e) {
                PIECE.requestFocus();
            }
        });
        
        GAME_OVER.setVisible(false);
        GAME_OVER.setFont(new Font(Font.DIALOG_INPUT, Font.BOLD, GAME_OVER.getHeight() / 12));
        
        SCORE.setFont(GAME_OVER.getFont());

        START.setFont(GAME_OVER.getFont());
        START.setBorder(null);
        START.setContentAreaFilled(false);
        START.setFocusPainted(false);
        START.addActionListener(e -> new Thread(this).start());

        NEXT.setPreferredSize(new Dimension(PIECE.getHeight() / 3, PIECE.getHeight() / 3));

        add(board, BorderLayout.WEST);
        add(statusPanel, BorderLayout.EAST);
        
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setResizable(false);
        setVisible(true);
        pack();
    }
    
    private synchronized boolean blockXY(int x, int y, Point[] block) {
        for (Point p : block) {
            if (x + p.x == -1 || x + p.x == X || y + p.y >= Y || (y + p.y > -1 && grid.get(y + p.y)[x + p.x] != null)) {
                return false;
            }
        }
        location = new Point(x, y);
        this.block = block;
        repaint();
        return true;
    }

    @Override
    public void run() {
        START.setVisible(false);
        grid = new ArrayList(Arrays.asList(new Color[Y][X]));        
        SCORE.setText("0");
        GAME_OVER.setVisible(false);
        
        String choice = I + O + J + L + T + S + Z;
        
        do {
            PIECE.setText(NEXT.getText());
            NEXT.setText(choice.split("")[new Random().nextInt(choice.length())]);
            location = new Point(X / 2, -2);
            block = (Point[]) SHAPE.get(PIECE.getText());                
            PIECE.setVisible(true);            

            do {
                try {
                    Thread.sleep(Math.max(MIN, MAX - Integer.parseInt(SCORE.getText()) * DELAY));
                } catch (Exception ex) { }
                PIECE.setVisible(blockXY(location.x, location.y + 1, block));
            } while (PIECE.isVisible());

            for (Point p : block) {
                try {
                    grid.get(location.y + p.y)[location.x + p.x] = (Color) COLOR.get(PIECE.getText());
                } catch (Exception ex) { 
                    GAME_OVER.setVisible(true);
                }
            }

            if (!GAME_OVER.isVisible()) {
                for (int y = 0; y < Y; y++) {
                    if (!Arrays.asList(grid.get(y)).contains(null)) {
                        grid.set(y, new Color[X]);
                        repaint();
                        try {
                            Thread.sleep(800);
                        } catch (Exception ex) { }
                        grid.add(0, grid.remove(y));
                        SCORE.setText("" + (Integer.parseInt(SCORE.getText()) + 1));
                    }
                }
                choice = (I + O + J + L + T + S + Z).replace(NEXT.getText(), NEXT.getText().replace(PIECE.getText(), ""));
            } else {
                try {
                    Thread.sleep(1200);
                } catch (Exception ex) { }
                START.setVisible(true);
                START.requestFocus();
            }
        } while (!GAME_OVER.isVisible());
    }
    
    public static void main(String[] args) {
        SHAPE.put(I, new Point[] {new Point(0, -2), new Point(0, -1), new Point(0, 0), new Point(0, 1)});
        SHAPE.put(O, new Point[] {new Point(0, 0), new Point(-1, 0), new Point(0, 1), new Point(-1, 1)});
        SHAPE.put(J, new Point[] {new Point(0, -1), new Point(0, 0), new Point(0, 1), new Point(-1, 1)});
        SHAPE.put(L, new Point[] {new Point(0, -1), new Point(0, 0), new Point(0, 1), new Point(1, 1)});
        SHAPE.put(T, new Point[] {new Point(-1, 0), new Point(0, 0), new Point(1, 0), new Point(0, 1)});
        SHAPE.put(S, new Point[] {new Point(1, 0), new Point(0, 0), new Point(0, 1), new Point(-1, 1)});
        SHAPE.put(Z, new Point[] {new Point(-1, 0), new Point(0, 0), new Point(0, 1), new Point(1, 1)});
        
        COLOR.put(I, Color.cyan);
        COLOR.put(O, Color.yellow);
        COLOR.put(J, Color.blue);
        COLOR.put(L, Color.orange);
        COLOR.put(T, Color.magenta);
        COLOR.put(S, Color.green);
        COLOR.put(Z, Color.red);
        
        new Tetris().setLocationRelativeTo(null);
    }
    
}
EN

回答 2

Code Review用户

回答已采纳

发布于 2022-06-20 19:39:54

我的代码是不是太简单了?

我们需要对它进行一点解压缩,因为我们可能对“简单”的不同定义进行了操作。您的应用程序只有一个文件和一个顶级类。虽然这在某种意义上很简单,但这并不是一个很好的主意,因为它阻碍了可维护性和可读性,而且实际上在认知负载方面意味着更多的复杂性--理解代码是如何布局的,并且能够对代码进行有意义的修改和测试。您应该尝试细分和模块化。

一个厌倦战斗的程序员对于简单性的看法是完全不同的,它涉及到更小、更好定义的类;更好的关注点分离;等等。

在更细的层面上:

您的单字母常量是一种代码气味,表明您实际上是试图捕捉某种类型的枚举。

我认为SHAPECOLOR不应该大写;需要<>类型参数;重要的是将它们内联地初始化,而不是在实例之外初始化,比如

代码语言:javascript
复制
    private final Map<String, Point[]> shapes = Map.ofEntries(
        entry(I, new Point[] {new Point( 0, -2), new Point( 0, -1), new Point(0, 0), new Point( 0, 1)}),
        entry(O, new Point[] {new Point( 0,  0), new Point(-1,  0), new Point(0, 1), new Point(-1, 1)}),
        entry(J, new Point[] {new Point( 0, -1), new Point( 0,  0), new Point(0, 1), new Point(-1, 1)}),
        entry(L, new Point[] {new Point( 0, -1), new Point( 0,  0), new Point(0, 1), new Point( 1, 1)}),
        entry(T, new Point[] {new Point(-1,  0), new Point( 0,  0), new Point(1, 0), new Point( 0, 1)}),
        entry(S, new Point[] {new Point( 1,  0), new Point( 0,  0), new Point(0, 1), new Point(-1, 1)}),
        entry(Z, new Point[] {new Point(-1,  0), new Point( 0,  0), new Point(0, 1), new Point( 1, 1)})
    );

    private final Map<String, Color> colors = Map.ofEntries(
        entry(I, Color.cyan),
        entry(O, Color.yellow),
        entry(J, Color.blue),
        entry(L, Color.orange),
        entry(T, Color.magenta),
        entry(S, Color.green),
        entry(Z, Color.red)
    );

colorsshapes具有相同的基数,这意味着需要捕获一个类,其中包含pointscolor的成员。

表达式grid.get(y)[x]需要进入一个变量以便重用。

不要new Integer(1),只需写1。

这个谓词:

代码语言:javascript
复制
if (x + p.x == -1 || x + p.x == X || y + p.y >= Y || (y + p.y > -1 && grid.get(y + p.y)[x + p.x] != null)) {

应以行分隔:

代码语言:javascript
复制
        if (x + p.x == -1
            || x + p.x == X
            || y + p.y >= Y
            || (y + p.y > -1 && grid.get(y + p.y)[x + p.x] != null)
        ) {

对您来说,在循环中在new Random()的内部执行run()可能不是一个好主意。您应该有一个Random实例,可能是类中的一个成员,但至少要从该循环中删除。

catch (Exception ex)是程序调试能力的毒药.它需要消失,如果您担心来自try块的一个特定异常,那么就用它代替。

票数 1
EN

Code Review用户

发布于 2022-06-18 19:19:48

分离关注点

您似乎把所有的代码都放在一个类中,这使得开始时很容易,但是添加的功能越多,就越难。在一个地方拥有所有东西都会降低代码的可修改性和可理解性,因为同时发生的事情太多了。

这就是为什么在分离不同关注点的过程中存在很多模式,这样它们就可以相互独立地修改。我喜欢MVVM。在MVVM中,至少有3个类:一个用于逻辑,一个用于UI和一个连接层。

长方法

您的方法非常长,有多个层次的嵌套。这可能是很难阅读和理解。

我喜欢使用以下一般的经验法则:

  • 该方法是否大于5行?-> 提取是其中的一部分。
  • 该方法是否有多个层次的缩进?例如嵌套的ifs或循环?->提取,直到它只有一个级别缩进。

将代码块提取到函数中使您可以为该块指定名称。这个名称应该清楚地说明块的意图,而不是实现。这样,只有当读者还想知道方法是如何实现的时候,读者才不需要阅读方法内容才能理解该方法正在做什么。

要获得更多的深度信息,您可以查看我在codereview元版上发表的关于可读代码的文章

票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/277461

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档