0

I am trying to make a simple screen-saver, and I think I have everything else implemented properly. I just don't know how to make the shapes move. Looking at the code a bit more, I think it has something to do with the way I might have implemented the shapes themselves (the if (line), etc.)? I have looked at other answers regarding this question, but I couldn't quite find the answer I was looking for with the way my code is implemented. Any advice is super helpful, thank you!

Also, just for a side note, is the -40 necessary for the "if" conditions? I thought I heard somewhere that it is useful to leave a little space in-between the frame, but I can't remember why.

import javax.swing.*;
import javax.swing.Timer;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.*;

public class MyScreenSaver extends JComponent
{   
    ArrayList <Shapes> randomShapes = new ArrayList <>();
    Shapes randomShape;
    public MyScreenSaver ()
    {
        class TimerListener implements ActionListener
        {
            @Override
            public void actionPerformed(ActionEvent event)
            {
                // Create random generation of colors and shapes...
                Shape shape = null;
                int frameWidth = 800;
                int frameHeight = 500;
                for (int i = 1; i <= 10; i ++)
                {
                    Random rng = new Random();
                    boolean line = rng.nextBoolean();
                    boolean rectangle = rng.nextBoolean();
                    
                    int red = ((int) (Math.random() * 255));
                    int green = ((int) (Math.random() * 255));
                    int blue = ((int) (Math.random() * 255));
                    Color color = new Color (red, green, blue);
        
                    int width = (10 + (int) (40 * Math.random()));
                    int height = (10 + (int) (40 * Math.random()));
                    int x = (int) (Math.random() * (getWidth() - width));
                    int y = (int) (Math.random() * (getHeight() - height));
                    int velX = 2;
                    int velY = 2;
                    int newX = velX + x;
                    int newY = velY + y;
                     
                    if (line)
                    {
                        shape = new Line2D.Double(x, y, x + width, y + height);
                    }
                    else if (rectangle)
                    {
                        shape = new Rectangle2D.Double(x, y, width, height);
                    }
                    else
                    {
                        shape = new Ellipse2D.Double(x, y, width, height);
                    }
                    // Here, we want the shapes to stop appearing after reaching a certain size...
                    if (randomShapes.size() >= 20)
                    {
                        break;
                    }
                    Shapes randomShape = new Shapes (color, shape);
                    // Add the shapes to the randomShapes ArrayList...
                    randomShapes.add (randomShape);
                    // Here, we are moving the shapes...
                    for (Shapes shapeMove : randomShapes)
                    {
                        if (x < 0 || x > frameWidth - 40)
                        {
                            velX = velX * -1;
                        }
                        else
                        {
                            x = newX;
                        }
                        if (y < 0 || y > frameHeight - 40)
                        {
                            velY = velY * -1;
                        }
                        else
                        {
                            y = newY;
                        }
                    }
                    repaint();
                }  
            }
        }
        ActionListener listener = new TimerListener();

        final int DELAY = 100;
        Timer t = new Timer(DELAY, listener);
        t.start(); 
    }
    @Override
    protected void paintComponent(Graphics g) 
    {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g.create();
        for (Shapes shape : randomShapes) 
        {
            System.out.println (randomShapes.size());
            shape.paint(g2d);
        }
        g2d.dispose();
    } 
    public static void main (String [] args)
    {
        // Set up the main frame...
        final int FRAME_WIDTH = 800;
        final int FRAME_HEIGHT = 500;
        JFrame screenSaverFrame = new JFrame ();
        screenSaverFrame.setTitle("Homework 6");
        screenSaverFrame.setSize (FRAME_WIDTH, FRAME_HEIGHT);
        screenSaverFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        final MyScreenSaver component = new MyScreenSaver ();
        screenSaverFrame.add (component);
        screenSaverFrame.setVisible (true);
    }
}

Here is the loop specifically...

for (Shapes shapeMove : randomShapes)
{
    if (x < 0 || x > frameWidth - 40)
    {
        velX = velX * -1;
    }
    else
    {
        x = newX;
    }
    if (y < 0 || y > frameHeight - 40)
    {
        velY = velY * -1;
    }
    else
    {
        y = newY;
    }
}

I was thinking about placing the contents of the loop inside a void method, but I really don't think that is necessary.

Also, here is my Shape class as well.

import java.awt.*;

public class Shapes 
{
    private final Color color;
    private final Shape shape;
    
    public Shapes (Color color, Shape shape)
    {
        this.color = color;
        this.shape = shape;
    }
    
    public Color getColor ()
    {
        return color;
    }
    
    public Shape getShape ()
    {
        return shape;
    }
    
    public void paint(Graphics2D g2d) 
    {
        g2d.setColor(color);
        g2d.draw(shape);
        g2d.fill(shape);
    }
}

JCD
  • 1
  • 1
    The logic in your TimeListener is incorrect. The logic to create the shapes should only be executed once at the start of your program. Your "Shapes" class should NOT be called "Shapes". It is a singular Shape. So you need to be more descriptive, maybe "ColoredShape"? In that class you need to add a "move()" method. The method will reset the x/y location of the Shape. So all your Timer code does is iterate through the ArrayList and invoke "move()" on all the objects in the ArrayLIst. See: https://stackoverflow.com/a/54028681/131872 for a working examples – camickr Oct 14 '20 at 05:08
  • @camickr, could you explain the TimeListener portion a little more? I am new to this in Java, and I am not sure how my logic is wrong. I did change the name and added a move method to my program. I am not too sure where I would place the move() method within the ScreenSaver class. Thank you. – JCD Oct 16 '20 at 00:30
  • There should be no looping logic in your Timer. The Timer `IS` the loop in that it will automatically generate an event every 100ms at which time you move all the shapes to their new location. I provided you with a working example that demonstrates how to implement the move() method and how to invoke the move() method from your Timer. – camickr Oct 16 '20 at 02:04

1 Answers1

1

The following mre is based on your code. Note the comments documenting the changes made:

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.Timer;


public class MyScreenSaver extends JComponent
{

    private final ArrayList <ColoredShape> randomShapes = new ArrayList <>();

    //define constants
    private static final int FRAME_WIDTH = 800, FRAME_HEIGHT = 500, DELAY = 100, MAX_SHAPE_HEIGHT = 40,
            MAX_SHAPE_WIDTH = 40, MIN_SHAPE_HEIGHT = 10, MIN_SHAPE_WIDTH = 40, MAX_SHAPE_COUNT = 20,
            LINE_WIDTH = 5, X_STEP = 5, Y_STEP = 5;

    //define shape types
    enum ShapeType {LINE, RECTANGLE, ELIPSE};

    public MyScreenSaver ()
    {
        ActionListener listener = new TimerListener();
        Timer t = new Timer(DELAY, listener);
        t.start();
    }

    @Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        g2d.setStroke(new BasicStroke(LINE_WIDTH));
        for (ColoredShape shape : randomShapes) //draw stored shapes
        {
            shape.paint(g2d);
        }
        g2d.dispose();
    }

    //invoked repeatedly by Timer. Each time it adds a shape to the collection
    //moves all shapes and repaints
    class TimerListener implements ActionListener
    {

        @Override
        public void actionPerformed(ActionEvent event)
        {
            // Create random generation of colors and shapes and location
            //up to MAX_SHAPE_COUNT
            if (randomShapes.size() < MAX_SHAPE_COUNT)
            {
                int red = (int) (Math.random() * 255);
                int green = (int) (Math.random() * 255);
                int blue = (int) (Math.random() * 255);
                Color color = new Color (red, green, blue);

                int width = MIN_SHAPE_WIDTH + (int) (MAX_SHAPE_WIDTH * Math.random());
                int height = MIN_SHAPE_HEIGHT + (int) (MAX_SHAPE_HEIGHT* Math.random());
                int x = (int) (Math.random() * (getWidth() - width));
                int y = (int) (Math.random() * (getHeight() - height));

                //select a random shape type
                int typeIndex = (int) (Math.random()* ShapeType.values().length);
                ShapeType type = ShapeType.values()[typeIndex];

                // Add the shapes to the randomShapes ArrayList...
                randomShapes.add (new ColoredShape (type,color,x, y,width,height));
            }

            //move the shapes...
            for (ColoredShape shape : randomShapes)
            {
                int x = shape.getX();//get current x position

                if (x <= 0 || x > getWidth() - shape.getWidth())
                {
                    shape.setXDirection(-1*shape.getXDirection());//change direction
                }

                shape.setX(shape.getX() + shape.getXDirection()*X_STEP);//increment

                int y = shape.getY();

                if (y <= 0 || y > getHeight()- shape.getHeight())
                {
                    shape.setYDirection(-1*shape.getYDirection());
                }

                shape.setY(shape.getY() + shape.getYDirection()*Y_STEP);
            }

            repaint();
        }
    }

    public class ColoredShape
    {
        private int x, y;
        private final int width, height;
        private int xDirection, yDirection;
        private final Color color;
        private final ShapeType type;

        public ColoredShape(ShapeType type, Color color, int x, int y, int width, int height)
        {
            this.type = type;
            this.color = color;
            this.x = x;
            this.y = y;
            this.width = width;
            this.height = height;
            xDirection = yDirection = 1; //1 means increase value, -1 decrease value
        }

        public Color getColor ()
        {
            return color;
        }

        private Shape constructShape ()
        {
            //construct new shape using updated bounds
            if(type == ShapeType.LINE)
                return new Line2D.Double(x, y, x + width, y + height);
            else if (type == ShapeType.RECTANGLE)
                return new Rectangle2D.Double(x, y, width, height);
            else
                return new Ellipse2D.Double(x, y, width, height);
        }

        public void paint(Graphics2D g2d)
        {
            g2d.setColor(color);
            Shape shape = constructShape();
            g2d.draw(shape);
            g2d.fill(shape);
        }

        public int getX() {
            return x;
        }

        public void setX(int x) {
            this.x = x;
        }

        public int getY() {
            return y;
        }

        public void setY(int y) {
            this.y = y;
        }

        public int getXDirection() {
            return xDirection;
        }

        public void setXDirection(int xDirextion) {
            xDirection = xDirextion;
        }

        public int getYDirection() {
            return yDirection;
        }

        public void setYDirection(int yDirection) {
            this.yDirection = yDirection;
        }

        public int getWidth() {
            return width;
        }

        public int getHeight() {
            return height;
        }
    }

    public static void main (String [] args)
    {
        // Set up the main frame...
        JFrame screenSaverFrame = new JFrame ();
        screenSaverFrame.setTitle("Homework 6");
        screenSaverFrame.setSize (FRAME_WIDTH, FRAME_HEIGHT);
        screenSaverFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        screenSaverFrame.add (new MyScreenSaver ());
        screenSaverFrame.setVisible (true);
    }
} 

enter image description here

c0der
  • 18,467
  • 6
  • 33
  • 65