4
\$\begingroup\$

I'm learning really basic java at school but I learn on my own at home. I've had some experience with simple swing games, but this one exceeds all. I want any opinion and advice someone may contribute.

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;
        }
    }
}
\$\endgroup\$
2
\$\begingroup\$

I have some suggestions for your code.

Always add curly braces to loop & if

In my opinion, it's a bad practice to have a block of code not surrounded by curly braces; I saw so many bugs in my career related to that, if you forget to add the braces when adding code, you break the logic / semantic of the code.

Extract the expression to variables when used multiple times.

In your code, you can extract the expression into variables; this will make the code shorter and easier to read.

Before

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();

After

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();
}

There are other cases like this one in your code, I suggest you do the same thing for them (new Dimension(width, height), ect).

Always uses a copy of the array when returning it or receiving it

Most of the containers (Map, List, Arrays) in java are mutable (exception for some implementations). If you return an instance in a getter, any class that have access to it can modify the collection; you lose the control of your own data this way. To overcome that, you need to create a new copy of the array / convert the collection to an implementation that cannot be modified and then, return the value.

Always try to keep control over your own data, never share the collections directly with the others and when receiving a collection / array, copy the data to your internal collections.

Before

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

After

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

They are multiple ways to copy an array.

Uses getters instead of static variables

In the Board class, you use static variables to share the values; this is a bug and a bad habit, since the static variables will be shared across instances (if you create multiple instances of the board, the values will be changed in all instances). Instead, I suggest that you hide the data and create getters for them.

Replace the for loop with an enhanced 'for' loop

In your code, you don’t actually need the index provided by the loop, you can the enhanced version.

Before

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

After

for (int coord : coords) {
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.