Trò chơi java Tetris cơ bản

Aug 17 2020

Tôi đang học java cơ bản ở trường nhưng tôi tự học ở nhà. Tôi đã có một số kinh nghiệm với các trò chơi đu dây đơn giản, nhưng điều này vượt quá tất cả. Tôi muốn bất kỳ ý kiến ​​và lời khuyên ai đó có thể đóng góp.

public class Display {

    private JFrame frame;
    private Canvas canvas;

    private String title;
    private int width, height;

    public Display(String title, int width, int height) {
        this.title = title;
        this.width = width;
        this.height = height;

        createDisplay();
    }
    private void createDisplay() {
        frame = new JFrame(title);
        frame.setSize(width, height);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setResizable(false);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);

        canvas = new Canvas();
        canvas.setPreferredSize(new Dimension(width, height));
        canvas.setMaximumSize(new Dimension(width, height));
        canvas.setMinimumSize(new Dimension(width, height));

        frame.add(canvas);
        frame.pack();
    }
    public Canvas getCanvas() {
        return canvas;
    }
    public void addKeyListner(KeyAdapter ka) {
        canvas.addKeyListener(ka);
        canvas.requestFocus();
    }
}

public class Shape {

    private int[] coords;
    private int color;
    private int pos;

    public Shape(Shape shape) {
        this(shape.coords, shape.color, shape.pos);
    }
    public Shape(int[] coords, int color) {
        this(coords, color, 0);
    }
    public Shape(int[] coords, int color, int pos) {
        this.coords = coords;
        this.color = color;
        this.pos = pos;
    }
    public void rotate() {
        pos++;
        if (pos == 4) pos = 0;
    }
    public int color() {
        return color;
    }
    public int position() {
        return pos;
    }
    public int[] coordinates() {
        return coords;
    }
}

public class Game implements Runnable {

    private Display display;
    private Board board;

    private int width, height;
    private String title;

    private boolean running = false;
    private Thread gameThread;
    private int tickTime = 400;

    private BufferStrategy bs;
    private Graphics g;

    private KeyKeeper keyKeeper;

    public Game(String title, int width, int height) {
        this.width = width;
        this.height = height;
        this.title = title;
    }
    
    private void initTick() {
        while (running) {
                try {
                    gameThread.sleep(tickTime);
                } catch (InterruptedException ie) {}

                tick();

            }
    }

    private void init() {
        display = new Display(title, width, height);
        board = new Board(width - 100, height);
        keyKeeper = new KeyKeeper();
        display.addKeyListner(keyKeeper);
    }

    private void tick() {
        board.tick();
    }

    private void render() {
        bs = display.getCanvas().getBufferStrategy();
        if (bs == null) {
            display.getCanvas().createBufferStrategy(3);
            return;
        }
        g = bs.getDrawGraphics();
        //Draw Here!

        //background
        Tetris.drawBackground(g, board, 0, 0);

        // board
        Tetris.drawBoard(g, board, 0, 0);

        //shape
        Tetris.drawShape(g, board);

        //End Drawing!
        bs.show();
        g.dispose();
    }

    public void run() {
            init();

            while (running) {
                render();
            }
            stop();
    }

    public synchronized void start() {
        if (running) {
            return;
        }
        running = true;
        gameThread = new Thread(this);
        gameThread.start();
        
        initTick();
    }

    public synchronized void stop() {
        if (!running) {
            return;
        }
        running = false;
        try {
            gameThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    class KeyKeeper extends KeyAdapter {

        @Override
        public void keyPressed(KeyEvent ke) {
            if (ke.getKeyCode() == KeyEvent.VK_LEFT) {
                board.moveShape(-1, 0);
            } else if (ke.getKeyCode() == KeyEvent.VK_RIGHT) {
                board.moveShape(1, 0);
            } else if (ke.getKeyCode() == KeyEvent.VK_DOWN) {
                board.moveShape(0, 1);
            } else if (ke.getKeyCode() == KeyEvent.VK_UP) {
                board.hardDown();
            }
            else if (ke.getKeyCode() == KeyEvent.VK_SPACE) 
                board.rotateShape();
        }
    }
}

public class Board {

    public static int width, height;
    public static int tx, ty;
    public static int xts;
    public static int yts;

    private int[][] boardCoor;

    private int[][] coords;

    private Shape noShape;
    private Point shapeCoorPoint;
    private boolean[] shapeUsed;
    private int shapeCounter;

    public Board(int width, int height) {
        this.width = width;
        this.height = height;

        init();
    }

    private void init() {
        tx = 12;
        ty = 24;
        xts = width / tx;
        yts = height / ty;

        boardCoor = new int[tx][ty];
        for (int i = 0; i < ty; i++) {
            for (int j = 0; j < tx; j++) {
                boardCoor[j][i] = 7;
            }
        }

        coords = new int[][]{
            {0, 2, 4, 6},// I
            {1, 2, 3, 4},// Z
            {0, 2, 3, 5},// S
            {0, 2, 3, 4},// T
            {0, 2, 4, 5},// L
            {1, 3, 5, 4},// J
            {2, 3, 4, 5} // O
        };

        shapeCoorPoint = new Point();
        shapeUsed = new boolean[]{false, false, false, false, false, false, false};
        shapeCounter = 0;

        initShape();
    }

    public int[][] getBoard() {
        return boardCoor;
    }

    public Shape getShape() {
        return noShape;
    }

    public Point getShapeCoorPoint() {
        return shapeCoorPoint;
    }

    private void initShape() {
        boolean changeShape = true;
        int n;
        while (changeShape) {
            n = (int) (Math.random() * 7);
            if (!shapeUsed[n]) {
                noShape = new Shape(coords[n], n);
                shapeUsed[n] = true;
                shapeCounter++;
                changeShape = false;
            }
        }

        if (shapeCounter == 7) {
            shapeUsed = new boolean[]{false, false, false, false, false, false, false};
            shapeCounter = 0;
        }
        shapeCoorPoint.move(tx / 2 - 1, 0);
    }

    public void tick() {
        if (Tetris.canFall(this)) {
            shapeCoorPoint.translate(0, 1);
        } else {
            Tetris.update(this);

            clearLines();
            initShape();
        }
    }

    public boolean moveShape(int dx, int dy) {
        //dy=1 - down
        //dx=-1 - right
        //dx=1 - left
        // ~~~ strategy ~~~
        // create an instance point, then, check -
        //if legal, translate the shape point.
        Point instancePoint = new Point(shapeCoorPoint);
        instancePoint.translate(dx, dy);
        if (Tetris.isLegal(boardCoor, noShape, instancePoint)) {
            shapeCoorPoint.translate(dx, dy);
            return true;
        }
        return false;
    }

    public void hardDown() {
        boolean stop;
        do {
            stop = moveShape(0, 1);
        } while (stop);
    }

    public boolean rotateShape() {
        //~~~ strategy ~~~
        //create an instance shape, then, check -
        //if legal, rotate
        Shape instanceShape = new Shape(noShape);
        instanceShape.rotate();
        if (Tetris.isLegal(boardCoor, instanceShape, shapeCoorPoint)) {
            noShape.rotate();
            return true;
        }
        return false;
    }

    private void clearLines() {
        boolean isFilled;
        for (int row = 0; row < ty; row++) {
            isFilled = true;
            //check the first tile of the each rank
            for (int col = 0; col < tx; col++) {
                if (boardCoor[col][row] == 7) {
                    isFilled = false;
                    col = tx;
                }
            }

            if (isFilled) {

                for (int i = 0; i < tx; i++) {
                    for (int j = row; j > 0; j--) {
                        boardCoor[i][j] = boardCoor[i][j - 1];
                        boardCoor[i][j - 1] = 7;
                    }
                }
            }
        }
    }
}

public class Tetris {

        //~~~graphic drawings~~~
    public static void drawBackground(Graphics g, Board board, int x, int y) {
        g.setColor(Color.black);
        g.fillRect(x, y, board.width, board.height);

        g.setColor(Color.white);
        g.drawRect(x, y, board.width, board.height);

        g.setColor(Color.gray);
        for (int i = 1; i < board.ty; i++) {
            g.drawLine(x, y + i * board.yts,
                    x + board.width, y + i * board.yts);
        }
        for (int i = 1; i < board.tx; i++) {
            g.drawLine(x + i * board.xts, y,
                    x + i * board.xts, y + board.height);
        }
    }

    public static void drawBoard(Graphics g, Board board, int x, int y) {
        int[][] boardCoor = board.getBoard();
        int c;
        Color[] colors = new Color[]{
            Color.red, Color.blue, Color.orange, Color.magenta,
            Color.cyan, Color.green, Color.yellow, Color.black};

        for (int i = 0; i < board.ty; i++) {
            for (int j = 0; j < board.tx; j++) {
                c = boardCoor[j][i];
                g.setColor(colors[c]);
                g.fillRect(x + j * board.xts + 1, y + i * board.yts + 1,
                        board.xts - 1, board.yts - 1);
            }
        }
    }

    public static void drawShape(Graphics g, Board board) {
        Point point = board.getShapeCoorPoint();
        Shape shape = board.getShape();
        int[] coords = shape.coordinates();
        int pos = shape.position();
        int c = shape.color();
        Color[] colors = new Color[]{
            Color.red, Color.blue, Color.orange, Color.magenta,
            Color.cyan, Color.green, Color.yellow, Color.black};
        g.setColor(colors[c]);

        int[] arr;

            for (int i = 0; i < coords.length; i++) {
                arr = getXY(coords[i], pos, point);
                g.fillRect(
                        (arr[0]) * board.xts + 1, (arr[1]) * board.yts + 1,
                        board.xts - 1, board.yts - 1);
            }
    }

            // ~~~game rules~~~
    public static boolean canFall(Board board) {
        return canFall(board.getBoard(), board.getShape(), board.getShapeCoorPoint());
    }

    public static boolean canFall(int[][] boardCoor, Shape shape, Point point) {
        return canFall(boardCoor, shape.coordinates(), shape.position(), point);
    }

    public static boolean canFall(int[][] boardCoor, int[] coords, int pos, Point point) {
        int[] arr;
            for (int i = 0; i < coords.length; i++) {
                arr = getXY(coords[i], pos, point);
                if (arr[1] == Board.ty - 1 || boardCoor[arr[0]][arr[1] + 1] != 7) {
                    return false;
                }
            }
        return true;
    }

    public static boolean isLegal(Board board) {
        return isLegal(board.getBoard(), board.getShape(), board.getShapeCoorPoint());
    }

    public static boolean isLegal(int[][] boardCoor, Shape shape, Point point) {
        return isLegal(boardCoor, shape.coordinates(), shape.position(), point);
    }

    public static boolean isLegal(int[][] boardCoor, int[] coords, int pos, Point point) {
        int[] arr;
        for (int i = 0; i < coords.length; i++) {
            arr = getXY(coords[i], pos, point);
            if (arr[1] >= Board.ty || arr[1] < 0  ||
                arr[0] < 0        || arr[0] >= Board.tx || 
                boardCoor[arr[0]][arr[1]] != 7) {
                return false;
            }
        }
        return true;
    }

                //~~~technical functions~~~
    public static void update(Board board) {
        update(board.getBoard(), board.getShape(), board.getShapeCoorPoint());
    }

    public static void update(int[][] boardCoor, Shape shape, Point point) {
        update(boardCoor, shape.coordinates(), shape.color(), shape.position(), shape, point);
    }

    public static void update(int[][] boardCoor, int[] coords, int color, int pos, Shape shape, Point point) {
        int[] arr;
        for (int i = 0; i < coords.length; i++) {
            arr = getXY(coords[i], pos, point);
            boardCoor[arr[0]][arr[1]] = color;
        }
    }

    private static int[] getXY(int value, int pos, Point point) {
        int[] arr = new int[2];
        if (pos == 0) {
            arr[0] = value % 2 + point.x;
            arr[1] = value / 2 + point.y;
            return arr;

        } else if (pos == 1) {
            arr[0] = 2 - value / 2 + point.x;
            arr[1] = 1 + value % 2 + point.y;
            return arr;

        } else if (pos == 2) {
            arr[0] = 1 - value % 2 + point.x;
            arr[1] = 3 - value / 2 + point.y;
            return arr;

        } else {
            arr[0] = value / 2 - 1 + point.x;
            arr[1] = 2 - value % 2 + point.y;
            return arr;
        }
    }
}

Trả lời

2 Doi9t Aug 17 2020 at 07:27

Tôi có một số gợi ý cho mã của bạn.

Luôn thêm dấu ngoặc nhọn vào loop&if

Theo ý kiến ​​của tôi, việc có một khối mã không được bao quanh bởi các dấu ngoặc nhọn là một cách làm không tốt; Tôi đã thấy rất nhiều lỗi trong sự nghiệp của mình liên quan đến điều đó, nếu bạn quên thêm dấu ngoặc nhọn khi thêm mã, bạn sẽ phá vỡ logic / ngữ nghĩa của mã.

Trích xuất biểu thức thành các biến khi được sử dụng nhiều lần.

Trong mã của bạn, bạn có thể trích xuất biểu thức thành các biến; điều này sẽ làm cho mã ngắn hơn và dễ đọc hơn.

Trước

if (ke.getKeyCode() == KeyEvent.VK_LEFT) {
   board.moveShape(-1, 0);
} else if (ke.getKeyCode() == KeyEvent.VK_RIGHT) {
   board.moveShape(1, 0);
} else if (ke.getKeyCode() == KeyEvent.VK_DOWN) {
   board.moveShape(0, 1);
} else if (ke.getKeyCode() == KeyEvent.VK_UP) {
   board.hardDown();
} else if (ke.getKeyCode() == KeyEvent.VK_SPACE)
   board.rotateShape();

Sau

int keyCode = ke.getKeyCode();
if (keyCode == KeyEvent.VK_LEFT) {
   board.moveShape(-1, 0);
} else if (keyCode == KeyEvent.VK_RIGHT) {
   board.moveShape(1, 0);
} else if (keyCode == KeyEvent.VK_DOWN) {
   board.moveShape(0, 1);
} else if (keyCode == KeyEvent.VK_UP) {
   board.hardDown();
} else if (keyCode == KeyEvent.VK_SPACE) {
   board.rotateShape();
}

Có những trường hợp khác giống như trường hợp này trong mã của bạn, tôi khuyên bạn nên làm điều tương tự cho chúng ( new Dimension(width, height), v.v.).

Luôn sử dụng bản sao của mảng khi trả lại hoặc nhận nó

Hầu hết các vùng chứa (Bản đồ, Danh sách, Mảng) trong java đều có thể thay đổi được (ngoại lệ đối với một số triển khai). Nếu bạn trả về một thể hiện trong getter, bất kỳ lớp nào có quyền truy cập vào nó đều có thể sửa đổi tập hợp; bạn mất quyền kiểm soát dữ liệu của mình theo cách này. Để khắc phục điều đó, bạn cần tạo một bản sao mới của mảng / chuyển đổi bộ sưu tập thành một triển khai không thể sửa đổi và sau đó, trả về giá trị.

Luôn cố gắng giữ quyền kiểm soát dữ liệu của riêng bạn, không bao giờ chia sẻ trực tiếp bộ sưu tập với những người khác và khi nhận bộ sưu tập / mảng, hãy sao chép dữ liệu vào bộ sưu tập nội bộ của bạn.

Trước

public int[] coordinates() {
   return coords;
}

Sau

public int[] coordinates() {
   return Arrays.copyOf(coords, coords.length);
}

Chúng có nhiều cách để sao chép một mảng .

Sử dụng getters thay vì các biến tĩnh

Trong Boardlớp, bạn sử dụng các biến tĩnh để chia sẻ các giá trị; đây là một lỗi và một thói quen xấu, vì các biến tĩnh sẽ được chia sẻ giữa các trường hợp (nếu bạn tạo nhiều trường hợp của bảng, các giá trị sẽ bị thay đổi trong tất cả các trường hợp). Thay vào đó, tôi đề nghị bạn nên ẩn dữ liệu và tạo ra các ứng dụng cho chúng.

Thay thế forvòng lặp bằng vòng lặp 'for' nâng cao

Trong mã của bạn, bạn thực sự không cần chỉ mục được cung cấp bởi vòng lặp, bạn có thể phiên bản nâng cao.

Trước

 for (int i = 0; i < coords.length; i++) {
    //[...]
 }

Sau

for (int coord : coords) {
}