Search code examples
javaswingoopjframegraphics2d

Coding a game of Pool in Java - how can I define the balls and move them (via Graphics g)?


My plan is to design a simple game of Pool in Java.

OOP makes the most sense here for the balls. All the balls have the same functionality, so because of that it would be a good idea to make a Ball class that would handle the relative position of the ball and other variables such as when it goes in a hole it removes itself and increments your score. So when it hits a hole Ball.dispose (); would fit nicely.

My issue is that I do not know how to manipulate the ball and dispose of it. Also inorder to move it I rely on Thread.sleep instead of java.swing.timer because there is no available Action Performed method I can rely on.

How can I move the ball more easily and get rid of it when needed?

enter image description here

The green thing covering the ball is my way of erasing the last position of the ball by drawing a green oval over it.

import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;


public class Main extends JFrame{

     Ball redball = new Ball (285, 600, 20, 20, Color.RED); 

    //variables to control redball
    private int rX = redball.getX();
    private int rY = redball.getY();
    private final int rWidth = redball.getWidth();
    private final int rHeight = redball.getHeight();

    int Force = 30; 
    int Bearing = 20; // True Bearing

 public Main (){

         setSize(600, 800);
         setVisible(true); 
         setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
         setTitle("Pool");

    }
     public void paint(Graphics g){
         super.paint(g);

         // draw the table
         g.setColor (Color.GREEN);
         g.fillRect(100, 100, 400, 600);
         g.setColor (Color.BLACK);
         g.drawRect(99, 99, 401, 601);


             //draw start ball
         g.setColor(redball.getColor());
         g.fillOval(rX, rY, rWidth, rHeight);



         if (Force == 30){
         for (int i = Force; i > 0;i--){
             try {
                    Thread.sleep(100);
                } catch(InterruptedException ex) {
                    Thread.currentThread().interrupt();
                }
             Force--;
             if (rY > 98 + rWidth) {
                 rY = rY - i;
                 rX = rX + (Bearing/5);
             }

             g.fillOval(rX, rY, rWidth, rHeight);
             g.setColor(Color.GREEN);
             repaint ();
             g.fillOval(rX - (Bearing/5), rY + i, rWidth, rHeight); // repaint last ball
             g.setColor(Color.RED);
             repaint ();
          }
         }



         // Ball.dispose (redball);

     }

    public static void main(String[] args) {

        new Main();
    }

Here is the class for the ball

public class Ball {

    private int x;
    private int y;
    private int width;
    private int height;
    private Color color;

    public Ball (int x, int y, int width, int height, Color color)
    {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.color = color;
    }

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

    public int getX (){
        return this.x;
    }

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

    public int getY (){
        return this.y;
    }

    public void setWidth (int width){
        this.width = width;
    }

    public int getWidth (){
        return this.width;
    }

    public void setHeight (int height){
        this.height = height;
    }

    public int getHeight (){
        return this.height;
    }

    public void setColor (Color color){
        this.color = color;
    }

    public Color getColor (){
        return this.color;
    }

    public static void dispose(Ball ball) {

    ball = null; // if I call this method nothing happens

    }


}

Solution

  • What I would do is maintain a List<Ball> field of all active balls. Then each iteration, iterate through that list and update and paint the Balls. When a Ball goes away, it's simply a matter of removing it from the list.

    A consideration is: Who determines when its time to dispose of a ball? If something outside the Ball does, you have a few options, among others:

    1. As I mentioned above, you could simply remove the Ball from the list.
    2. You could set a "dead" flag in the Ball elsewhere, then on each iteration remove all Balls from the list whose "dead" flag has been set.

    If the Ball itself determines when it is time to go away, you have a few options, among others, there as well:

    1. Perhaps a method boolean Ball.think(), that updates the state of the ball and returns true if the Ball is still good, or false if its dead. Then when you iterate through your list of balls, you call think() on all of them to update, and remove ones from the List that returned false.
    2. Related to option 2 above, if a Ball has a think method or something similar, the ball can set its own "dead" flag (or something outside the Ball can set it), then the main loop can remove dead balls from the list.

    If you want to keep the Ball instance around and just not draw it, you have a few options there too:

    1. Simply skip processing of Balls marked as "dead" instead of removing them.
    2. Move "dead" balls to a different dead Ball list.

    Personally, I like the boolean think() method, because it easy allows you to specify a base interface for objects in the scene. You can also have objects paint themselves in that case. E.g.:

    interface Entity {
        public boolean think ();
        public void paint (Graphics g);
    }
    
    class Ball implements Entity {
        @Override public boolean think () {
            // return true if alive, false if dead
        }
        @Override public void paint (Graphics g) {
            // draw this ball
        }
    }
    
    // then in your main update loop:
    List<Entity> entities = ...;
    Iterator<Entity> eit = entities.iterator();
    while (eit.hasNext())
        if (!eit.next().think()) 
            eit.remove();
    
    // and in paint:
    for (Entity e:entities)
        e.paint(graphics);
    

    If you want to take the option I mentioned above of just skipping "dead" balls instead of removing them, something like this would be more appropriate (leaving out Ball for brevity), where isActive() returns true if the ball is active or false if it is temporarily "dead":

    interface Entity {
        public boolean isActive ();
        public void think (); // think returns nothing
        public void paint (Graphics g);
    }
    
    // then in your main update loop:
    List<Entity> entities = ...;
    for (Entity e:entities)
        if (e.isActive())
            e.think();
    // it is the responsibility of something outside the ball to restore it to an
    // active state, since think() isn't called if !isActive(). alternatively, you 
    // could always call think(), and just don't paint inactive balls.
    
    // and in paint:
    for (Entity e:entities)
        if (e.isActive())
            e.paint(graphics);
    

    Still, you don't have to do it that way, there are plenty of arguments for all of the options listed above, and more. In your application, for example, there's not much need for an Entity interface if you know you are only dealing with Balls; and think() might not be the most convenient way to go if all of your physics logic is happening somewhere else (although of course the code could be written to place the logic in Ball).

    As you can see there are many ways to skin a cat, but I hope something here helps.