4
\$\begingroup\$

I made an Atari breakout game from Java from an inspiration from Google's Atari Breakout Doodle.

Here is the code for the main Controller:

This actually works. Try it by putting it all in an atariBreakout folder.

package atariBreakout;

import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.*;

public class Controller implements ActionListener, ItemListener {
    Box box;
    Block block;
    ArrayList<Ball> balls = new ArrayList<Ball>();
    Label l = new Label("Game starting in 1");
    Label start = new Label("");
    Random ran = new Random();
    JMenuBar menuBar;
    JMenu menu;
    long score = 0;
    JMenuItem menuItem;
    java.awt.Image OSC;
    ArrayList<Image> images = new ArrayList<Image>();
    Random r = new Random();
    boolean paused = false;

    public static void main(String[] args) {
        Controller c = new Controller();
        c.startGame();
        System.out.println("Game Started...");
    }

    public void startGame() {
        box = new Box("Atari Breakout");
        box.setJMenuBar(createMenuBar());
        box.setVisible(true);
    }

    public JMenuBar createMenuBar() {
        menuBar = new JMenuBar();

        menu = new JMenu("Control");
        menu.setBackground(Color.BLUE);

        menuItem = new JMenuItem("Save Progress", KeyEvent.VK_T);

        menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
                ActionEvent.CTRL_MASK));
        menuItem.getAccessibleContext().setAccessibleDescription(
                "This save the progress of the game. ");

        menu.add(menuItem);
        menuBar.add(menu);

        menuItem = new JMenuItem("Pause/Resume", KeyEvent.VK_SPACE);
        menuItem.setAccelerator(KeyStroke.getKeyStroke(' '));
        menuItem.getAccessibleContext().setAccessibleDescription(
                "This save the progress of the game. ");
        menuItem.addActionListener(this);
        menu.add(menuItem);
        menuBar.add(menu);

        return menuBar;
    }

    @Override
    public void itemStateChanged(ItemEvent arg0) {

    }

    @Override
    public void actionPerformed(ActionEvent arg0) {
        System.out.println("Space pressed. ");
        paused = !paused;
    }

    class Box extends JFrame {
        Box(String s) {
            super(s);

            setLayout(new BorderLayout());
            GamePanel gp = new GamePanel();

            gp.add(l);

            setBounds(0, 0, 800, 800);
            setBackground(getBackground());

            block = new Block(0, 850, 400, 10, Color.BLUE);
            block.setDoubleBuffered(true);
            gp.add(block);

            for (int i = 5; i <= 11; i++) {
                for (int j = 1; j <= 24; j++) {
                    Color c = new Color(r.nextInt(255), r.nextInt(255),
                            r.nextInt(255));
                    Image img = new Image(j * 50, i * 50, 50, 50, c);
                    img.setDoubleBuffered(true);
                    images.add(img);
                    gp.add(img);
                }
            }
            for (int i = 0; i < 2; i++) {
                Ball ball = new Ball(r.nextInt(500), getHeight() - (3 - i)
                        * 100, 20, 4);
                ball.setDoubleBuffered(true);
                balls.add(ball);
                gp.add(ball);
            }
            Baller b = new Baller();
            Thread t = new Thread(b);

            addMouseMotionListener(new MyMouseListener());
            addKeyListener(new MyKeyListener());

            add(gp);

            setFocusable(true);
            setVisible(true);
            setExtendedState(Frame.MAXIMIZED_BOTH);
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

            block.degrees = 0;
            t.start();
        }
    }

    class MyKeyListener extends KeyAdapter {
        public void keyPressed(KeyEvent e) {
            if (e.getKeyCode() == KeyEvent.VK_LEFT) {
                block.degrees += 5;
                box.repaint();
            } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
                block.degrees -= 5;
                box.repaint();
            }
        }
    }

    class GamePanel extends JPanel {
        public void update(Graphics g) {
            paint(g);
        }

        public void paint(Graphics g) {
            Dimension d = getSize();
            checkOffscreenImage();
            Graphics offG = OSC.getGraphics();
            offG.setColor(Color.white);
            offG.fillRect(0, 0, d.width, d.height);
            paintOffscreen(OSC.getGraphics());
            g.drawImage(OSC, 0, 0, null);

        }

        private void checkOffscreenImage() {
            Dimension d = getSize();
            if (OSC == null || OSC.getWidth(null) != d.width
                    || OSC.getHeight(null) != d.height) {
                OSC = createImage(d.width, d.height);
            }
        }

        public void paintOffscreen(Graphics g) {
            block.paint(g);
            for (Ball ball : balls) {
                ball.paint(g);
            }
            for (Image img : images) {
                img.paint(g);
            }
        }
    }

    class Baller implements Runnable {

        @Override
        public void run() {
            boolean xplus = false;
            boolean yplus = false;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            } // initial start
            try {
                while (true) {

                    Thread.sleep(10);
                    for (Ball ball : balls) {
                        int velocity = ball.velocity;
                        if (paused)
                            ball.velocity = 0;
                        else
                            ball.velocity = velocity;


                        Random ran = new Random();
                        if (block.degrees == 180)
                            block.degrees = 0;
                        Rectangle b = new Rectangle((int) ball.x, (int) ball.y,
                                ball.radius, ball.radius);
                        Rectangle blockr = new Rectangle(block.x, block.y,
                                block.length, block.width);
                        if (b.intersects(blockr) && !ball.touchedLast) {
                            if (block.degrees >= 0)
                                ball.xDegrees = 2 * block.degrees
                                        - ball.xDegrees;
                            else if (block.degrees < 0)
                                ball.xDegrees = -ball.xDegrees + 2
                                        * block.degrees;
                            ball.blockTime = System.currentTimeMillis();
                            ball.touchedLast = true;
                        } else if (!b.intersects(blockr)
                                && ball.touchedLast
                                && System.currentTimeMillis() - ball.blockTime >= 700) {
                            ball.touchedLast = false;
                        }

                        if (ball.x <= 0) {
                            if (ball.xDegrees > 0)
                                ball.xDegrees = 180 - ball.xDegrees;
                            else
                                ball.xDegrees = -180 - ball.xDegrees;
                        }
                        if (ball.y <= 10) {
                            ball.xDegrees *= -1;
                        }
                        if (ball.x >= box.getWidth() - 80) {
                            if (ball.xDegrees < 0)
                                ball.xDegrees = -180 - ball.xDegrees;
                            else
                                ball.xDegrees = 180 - ball.xDegrees;
                        }
                        if (ball.y >= box.getHeight() - 100) {
                            System.out.println("You died");
                            break;
                        }
                        double ballXCalc = Math.cos(Math
                                .toRadians(ball.xDegrees)) * ball.velocity;
                        double ballYCalc = Math.sin(Math
                                .toRadians(ball.xDegrees)) * ball.velocity;
                        if (ball.xDegrees >= 0) {
                            if (ball.xDegrees < 90) {
                                ball.x += ballXCalc;
                                ball.y -= ballYCalc;
                            } else if (ball.xDegrees == 180) {
                                ball.x -= 1 * ball.velocity;
                                ball.y += 0;
                            } else if (ball.xDegrees > 90) {
                                ball.x += ballXCalc;
                                ball.y -= ballYCalc;
                            } else if (ball.xDegrees == 90) {
                                ball.x += 0;
                                ball.y -= 1 * ball.velocity;
                            }
                        } else if (ball.xDegrees < 0) {
                            if (ball.xDegrees > -90) {
                                ball.x += ballXCalc;
                                ball.y -= ballYCalc;
                            } else if (ball.xDegrees < -90) {
                                ball.x += ballXCalc;
                                ball.y -= ballYCalc;
                            } else if (ball.xDegrees == -90) {
                                ball.x += 0;
                                ball.y += 1 * ball.velocity;
                            }
                        }

                        System.out.println("ball x : " + ball.x + "ball y : "
                                + ball.y);

                        long time = 0;
                        for (Image img : images) {
                            Rectangle r = new Rectangle(img.x, img.y,
                                    img.length, img.width);
                            if (b.intersects(r) && !ball.touchedImage) {
                                img.width = 0;
                                img.length = 0;
                                ball.xDegrees *= -1;
                                score += 10;
                                l.setText("Score: " + String.valueOf(score));
                            }
                        }
                    }
                    box.repaint();
                }
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }

    }

    class MyMouseListener extends MouseMotionAdapter {
        public void mouseMoved(MouseEvent e) {
            int x = e.getX();
            block.x = x - block.length / 2;
            box.repaint();
        }
    }

}

Ball class:

package atariBreakout;

import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.util.Random;

import javax.swing.JPanel;

public class Ball extends JPanel{
    public double x;
    public double y;
    public int radius;
    public int velocity;
    public Color color;
    public double yDir;
    public double xDegrees;
    public boolean touchedLast = false;
    public boolean touchedImage = false;
    public boolean touchedXRight = false;
    public long ballXTime;
    public long imgTime;
    public long blockTime;
    public Ball(double x, double y, int radius, int velocity){
        this.x = x;
        this.y = y;
        this.radius = radius;
        this.velocity = velocity;
        this.yDir = 1;
        this.xDegrees = -90;
    }
    public void paint(Graphics g){
        g.setColor(Color.BLACK);
        g.fillOval((int)Math.ceil(x), (int)Math.ceil(y), radius, radius);
    }
}

Block (the thing that you can move) part:

package atariBreakout;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JPanel;

public class Block extends JPanel{
    public int x;
    public int y;
    public int length;
    public int width;
    public Color color;
    public int degrees;
    Block(int x, int y, int length, int width, Color color){
        this.x = x;
        this.y = y;
        this.length = length;
        this.width = width;
        this.color = color;
    }
    public void paint(Graphics g){
        if(this.degrees >= 180)this.degrees -= 180;
        //else if(this.degrees <= 0) this.degrees += 180;
        Graphics2D g2 = (Graphics2D) g;
        g2.rotate(-Math.toRadians(degrees), x+this.length/2, y+this.width/2);
        g2.setColor(this.color);
        g2.fillRect(this.x, this.y, this.length, this.width);
        g2.rotate(+Math.toRadians(degrees), x+this.length/2, y+this.width/2);
    }
}

Image (the things that are up there) class:

package atariBreakout;

import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;

import javax.swing.JPanel;

@SuppressWarnings("serial")
public class Image extends JPanel{
    public int x;
    public int y;
    public int length;
    public int width;
    public Color color;
    public Image(int x, int y, int length, int width, Color color){
        this.x = x;
        this.y = y;
        this.length = length;
        this.width = width;
        this.color = color;
    }
    public void paint(Graphics g){
        g.setColor(this.color);
        g.fillRect(x, y, length, width);
    }
    public boolean equals(Object o){
        if(o instanceof Image){
            Image img = (Image) o;
            if(img.x == this.x && img.y == this.y) return true;
            else return false;
        } else return false;
    }
}

How can I revise these codes so it's shorter, faster, and better?

Put all these in one folder called atariBreakout and run using Java because it's in a package. The block can tilt using and , and the game can pause using space.

\$\endgroup\$
1
  • \$\begingroup\$ My resolution must be different than yours because the paddle is offscreen \$\endgroup\$ Dec 17, 2015 at 2:49

1 Answer 1

2
\$\begingroup\$

Making classes is a good thing to help encapsulate specific work on an object is a good habbit. Having nested classes is not so good, use in very rare situations. That said It would be better if you would move your nested classes outside Controller and into a seperate file. I think I see why you had it the way you do because you want to change some of the fields in your java classes. Classes are passed by reference. To show you in code and with a test consider the following code

@Test
public void testUpdatingFieldInADifferentClass() throws  Exception {
    Box box = new Box();
    BoxChanger changer = new BoxChanger(box);
    box.X = 1;
    box.Y = 3;

    changer.changeBox();

    assertEquals(box.X, 2, "Box Changer should have added 1 to X");
    assertEquals(box.Y, 3, "Box Changer should Not have Touched Y");
}

I create a new instance of a box, and pass it in immediatly to BoxChanger. BoxChanger is this very simple classes:

public class BoxChanger {
    private final Box box;

    public BoxChanger(Box box) {
        this.box = box;
    }

    public void changeBox(){
        box.X += 1;
    }
}

and when I run that test my test passes just fine. You might wonder how that is possible, and the reason is because of being passed in by reference and not by value. So anychanges I make to that specific instance of Box in any class will be "updated" wherever that instance is used. Hence how that test passes. How to use this? Lets extract one of your listeners into its own file.

public class MyKeyListener extends KeyAdapter {
    private final Block block;
    private final Component box;

    MyKeyListener(Component box, Block block) {
        this.box = box;
        this.block = block;
    }

    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_LEFT) {
            block.degrees += 5;
            box.repaint();
        } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
            block.degrees -= 5;
            box.repaint();
        }
    }
}

You'll see that I marked block and box as final which would force me to make sure that I never set block or box to anything different than what it already is. Or in other words don't change the reference to which block and box point to. Now with a slight change in Controller.Box to make the compiler happy. addKeyListener(new MyKeyListener(this, block));. Fire up the game and sure enough the program still works. We would call this encapsulation. It's a form of refactoring that is fairly safe because you're not really changing anything, you're just moving things around to make more sense and to clean up the code some. As an exercise I'll let you try to do the same with MyMouseListener, Box, GamePanel, and Baller.

Next point that I want to point to is having public fields. In your code there are very many places that you set the field to a specific value. such as in your keylistener block.degrees += 5. It would be much better to make Block have a public method called rotateLeft, rotateRight. You could make a rotate(int) but the amount that you want to rotate might change and it is easier to update 1 reference instead of many. Changes to Block:

private int degrees;
private static final int ROTATION = 5;
public void rotateRight(){
    rotate(-ROTATION);
}
public void rotateLeft(){
    rotate(ROTATION);
}
private void rotate(int amount){
    degrees += amount;
    repaint();
}

when I change degrees to private the compiler will now show me a few places where degrees is used. One in particular that stands out is in Controller.Baller

if (block.degrees == 180)
    block.degrees = 0;

This is the sorta stuff that very desperatly needs to be in block and not anywhere else. So a quick rule change to Block.rotate and a getter added to degrees and we are set. Block and the changes to Block now look like

private int degrees;
private static final int ROTATION = 5;

public int getDegrees(){
    return degrees;
}
public void rotateRight(){
    rotate(-ROTATION);
}
public void rotateLeft(){
    rotate(ROTATION);
}
private void rotate(int amount){
    degrees += amount;
    if (degrees == 180)
        degrees = 0;
    repaint();
}

Normally I would also mention testing, and one place in particular that I would like to get under some sort of test framework is Baller because there is some logic in there that needs to get tested. specically there is a bug (as far as I can tell) that if my paddle is at a steep angle the collision detection is wrong. I'm not sure specifically where it is wrong, but the ball fell right through my paddle but did move slighly (almost like there was a hole in my paddle that the ball fell into). That is all I have for now.

\$\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.