Search code examples
javaswingawt

Changing the Behavior of Balls Depending on the Grid They Touch (JAVA Swing)


I have a frame that I painted half black and half white with Java Swing's grid methods. And on this frame, there is a white ball that starts moving on the black part and a black ball that starts moving on the white part. I want these balls to bounce back if they touch grids that are the same color as them and paint the grid they touch in the opposite color. I have completed everything except the last feature, but I can't figure out how to implement the last feature in the application.

Interface.java

import javax.swing.*;
import java.awt.*;

public class Interface extends JFrame{
    
    private int frameHeight = 637;
    private int frameWidth = 1214;
    
    public Interface() {

        setSize(frameWidth, frameHeight);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLayout(new BorderLayout());
        setTitle("Bouncing Ball");
        add(new Core());
        setVisible(true);

    }

    public static void main(String[] args){

        new Interface();

    }

}

Core.java

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Core extends JPanel{
    
    int xw = 10, yw = 10, xb = 1000, yb = 400;
    int bounds = 20, gridSize = 20;
    int vxw = 5, vyw = 5, vxb = 5, vyb = 5;
    int rows, columns;

    public Core() {

        rows = getHeight() / gridSize;
        columns = getWidth() / gridSize;
        
        Timer timer = new Timer(10, new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                
                System.out.println(getHeight());
                System.out.println(getWidth());
                
                xw += vxw;
                yw += vyw;

                xb -= vxb;
                yb -= vyb;

                if (xw > getWidth() - bounds || xw < 0) {vxw = -vxw;}
                if (yw > getHeight() - bounds || yw < 0) {vyw = -vyw;}
                if (xb > getWidth() - bounds || xb < 0) {vxb = -vxb;}
                if (yb > getHeight() - bounds || yb < 0) {vyb = -vyb;}

                repaint();

            }
            
        });

        timer.start();

    }

    public void paintComponent(Graphics g) {

        Graphics2D g2dw = (Graphics2D) g;
        Graphics2D g2db = (Graphics2D) g;
        Graphics2D grid = (Graphics2D) g;
        
        super.paintComponent(g);    
            
            for (int k = 0; k < getWidth(); k += gridSize) {
                for (int z = 0; z < getHeight(); z += gridSize) {
                     
                    grid.drawRect(k, z, gridSize, gridSize);

                     if (k < getWidth() / 2) {
                        grid.setColor(Color.BLACK);
                        grid.fillRect(k, z, gridSize, gridSize);
                     } else {
                        grid.setColor(Color.WHITE);
                        grid.fillRect(k, z, gridSize, gridSize);

                     }
                }
            }
        

            g2dw.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2dw.setColor(Color.white);
            g2dw.fillOval(xw, yw, bounds, bounds);
            //g2d.dispose();

            g2db.setColor(Color.black);
            g2db.fillOval(xb, yb, bounds, bounds);

    }

    public static void main(String[] args){

        

    }

}

Example Output: https://github.com/vnglst/pong-wars

I'm trying to learn how to write a feature that will make the balls bounce when they touch a grid with the same color as them and paint the grid they touch with the opposite color. Any hints or guidance would be much appreciated too, if you don't want to bother with writing Java code.

I tried creating a 2D array that contains 1 or 0 values (1 for white grid, 0 for black) but encountered with IndexBound errors.


Solution

  • According to camickr's instructions, there is the Java code:

    Core.java

    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.*;
    import java.util.ArrayList;
    
    class Core extends JPanel {
    
        //Adjusting the starting points of balls and other variables
        protected int xw = Interface.frameWidth / 2 - 40, yw = Interface.frameHeight / 2, xb = Interface.frameWidth / 2 + 40, yb = Interface.frameHeight / 2;
        private int bounds;
        private int rows, columns, tileSize;
        private ArrayList<ColoredShape> tiles;
        private ArrayList<Ball> balls;
    
        public Core(int rows, int columns, int tileSize) {
            
            this.rows = rows;
            this.columns = columns;
            this.tileSize = tileSize;
            this.bounds = tileSize / 2;
            this.tiles = new ArrayList<>();
            this.balls = new ArrayList<>();
    
            // Creating tiles
            for (int i = 0; i < rows; i++) {
                for (int j = 0; j < columns; j++) {
                    
                    int x = j * tileSize;
                    int y = i * tileSize;
                    Shape rect = new Rectangle(x, y, tileSize, tileSize);
                    Color color = (j < columns / 2) ? Color.BLACK : Color.WHITE;
                    tiles.add(new ColoredShape(rect, color));
    
                }
            }
    
            // Creating balls
            balls.add(new Ball(xw, yw, bounds, Color.WHITE, 10, 3));
            balls.add(new Ball(xb, yb, bounds, Color.BLACK, 10, -3));
    
            // Animation part
            Timer timer = new Timer(10, new ActionListener() {
                
                @Override
                public void actionPerformed(ActionEvent e) {
                    
                    moveBalls();
                    checkCollision();
                    repaint();
                }
    
            });
    
            timer.start();
        }
    
        //Moving balls
        private void moveBalls() {
            for (Ball ball : balls) {
                
                ball.setX(ball.getX() + ball.getVx());
                ball.setY(ball.getY() + ball.getVy());
        
                // Bouncing back from walls
                if (ball.getX() > getWidth() - ball.getShape().width || ball.getX() < 0) {
                    ball.setVx(-ball.getVx());
                }
                if (ball.getY() > getHeight() - ball.getShape().height || ball.getY() < 0) {
                    ball.setVy(-ball.getVy());
                }
    
            }
        }
    
        private void checkCollision() {
            
            for (Ball ball : balls) {
                
                for (ColoredShape tile : tiles) {
                    
                    if (ball.getShape().getBounds().intersects(tile.getShape().getBounds())) {
                        
                        if (ball.getColor().equals(tile.getColor())) {
                            
                            tile.setColor(ball.getColor().equals(Color.WHITE) ? Color.BLACK : Color.WHITE);
                            
                            // Calculating the angle of incidence
                            double tileCenterX = tile.getShape().getBounds2D().getCenterX();
                            double tileCenterY = tile.getShape().getBounds2D().getCenterY();
                            double ballCenterX = ball.getShape().getCenterX();
                            double ballCenterY = ball.getShape().getCenterY();
    
                            double angle = Math.atan2(ballCenterY - tileCenterY, ballCenterX - tileCenterX);
    
                            // Adjusting velocity based on angle of incidence
                            if (Math.abs(angle) < Math.PI / 4 || Math.abs(angle) > 3 * Math.PI / 4) {
                                // Adjusting x velocity
                                ball.setVx(-ball.getVx());
                            } else {
                                // Adjusting y velocity
                                ball.setVy(-ball.getVy());
                            }
    
                        }
                        
                    }
                }
            }
        }
    
        public void paintComponent(Graphics g) {
            
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g;
    
            // Drawing tiles
            for (ColoredShape tile : tiles) {
                g2d.setColor(tile.getColor());
                g2d.fill(tile.getShape());
            }
    
            // Drawing balls
            for (Ball ball : balls) {
                g2d.setColor(ball.getColor());
                g2d.fill(ball.getShape());
            }
        }
    
        @Override
        public Dimension getPreferredSize() {
            return new Dimension(columns * tileSize, rows * tileSize);
        }
    
    }
    

    Interface.java

    import javax.swing.*;
    import java.awt.*;
    import java.awt.geom.Ellipse2D;
    
    public class Interface extends JFrame {
    
        protected static int frameHeight = 637;
        protected static int frameWidth = 637;
        private int tileSize = 30;
    
        public Interface() {
    
            setSize(frameWidth, frameHeight);
            setLocationRelativeTo(null);
            setDefaultCloseOperation(EXIT_ON_CLOSE);
            setLayout(new BorderLayout());
            setTitle("Bouncing Ball");
            add(new Core(frameHeight / tileSize, frameWidth / tileSize, tileSize)); // Rows, Columns, TileSize
            setVisible(true);
    
        }
    
        public static void main(String[] args) {
            new Interface();
        }
    
    }
    
    class ColoredShape {
        
        private Shape shape;
        private Color color;
    
        public ColoredShape(Shape shape, Color color) {
            
            this.shape = shape;
            this.color = color;
    
        }
    
        public Shape getShape() {
            return shape;
        }
    
        public Color getColor() {
            return color;
        }
    
        public void setColor(Color color) {
            this.color = color;
        }
    
    }
    
    class Ball {
        
        private Ellipse2D.Double shape;
        private Color color;
        private int vx;
        private int vy;
    
        public Ball(int x, int y, int diameter, Color color, int vx, int vy) {
            
            this.shape = new Ellipse2D.Double(x, y, diameter, diameter);
            this.color = color;
            this.vx = vx;
            this.vy = vy;
    
        }
    
        public Ellipse2D.Double getShape() {
            return shape;
        }
    
        public Color getColor() {
            return color;
        }
    
        public int getVx() {
            return vx;
        }
    
        public void setVx(int vx) {
            this.vx = vx;
        }
    
        public int getVy() {
            return vy;
        }
    
        public void setVy(int vy) {
            this.vy = vy;
        }
    
        public int getX() {
            return (int) shape.x;
        }
    
        public void setX(int x) {
            shape.x = x;
        }
    
        public int getY() {
            return (int) shape.y;
        }
    
        public void setY(int y) {
            shape.y = y;
        }
        
    }
    

    There was a logic error in your code, I fixed it:

    • When the balls touched a square, both the x and y direction variables changed direction, which prevented a realistic collision, the balls would collide in a symmetrical pattern after a certain period of time in the same way forever. I fixed it by calculating the collision angle as in the example code you provided.
    • Still has a repetitive infrastructure where I don't randomize the starting directions of the balls, but it can easily be fixed.