2

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

    }


}
Jebathon
  • 4,310
  • 14
  • 57
  • 108
  • 1
    See also this [Q&A](http://stackoverflow.com/q/345838/230513); one of the [answers](http://stackoverflow.com/a/6794843/230513) cites a Java implementation. – trashgod Aug 08 '13 at 16:48

2 Answers2

2

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.

Jason C
  • 38,729
  • 14
  • 126
  • 182
  • 1
    Edited to add examples of other options, including options that keep unused Ball references around. I'm done with all these edits now. – Jason C Aug 08 '13 at 16:13
  • Very thought-out, descriptive answer. – Xynariz Aug 08 '13 at 16:53
  • @Jason C - Thankyou, very articulate; Il accept this question. Im just wondering if can I use this iterator via the Ball class to move the ball and remove it at the same time while following a path? I am having issues repainting the ball after it moves and I don't know if its efficient drawing a background color ball to mask its last position. – Jebathon Aug 08 '13 at 17:03
  • 1
    As hinted at in my comment on Gilbert's answer, you're going to want to only paint from JFrame's `paintComponent` (as you are almost already doing - you should use `paintComponent`, not `paint`). The general method for painting is: Erase entire background -> redraw all balls at their current locations. This will produce the desired effect. As an optimization (but do not optimize prematurely), you could just redraw background in the dirty regions and under old ball positions (or add old ball positions to a "dirty" region that you maintain). For start, though, just erase whole bg and redraw. – Jason C Aug 08 '13 at 17:10
  • 1
    In your original code, where you are drawing the BG under each ball in your loop, you're doing it *after* you move the ball, but you'd want to do that *before* you move the ball, otherwise you will leave a trail. – Jason C Aug 08 '13 at 17:12
  • 1
    Also responding to your comment in `public static void dispose (Ball ball) { ball = null; }` -- no that would of course do nothing, all you are doing is setting the local parameter variable, `ball`, to `null`. It has no effect outside that method. – Jason C Aug 08 '13 at 17:13
2

Here are some suggestions.

  • Create a Rack class that contains all of the balls for your game.

  • Move the draw code for a ball into your Ball class. I realize that this suggestion is not pure MVC, but games are a lot easier to code when objects draw themselves.

  • Don't try to repaint part of the screen. It's a lot easier, and pretty fast, to just redraw the entire screen.

  • Don't dispose any Ball instances. Move it to some negative position that your draw code recognizes and doesn't draw.

When you're coding an action game, your main loop should look something like this (psudeocode).

while (running) {
    calculateRackPosition();
    drawRack();
    Thread.sleep(100L);
}

You incrementally move the balls, then redraw the screen. This is how you code any animation. This code would run while the balls are moving.

You would write other code for when the person is aiming his shot.

Gilbert Le Blanc
  • 50,182
  • 6
  • 67
  • 111
  • An important caveat: If you're painting directly from another thread, you'll have to use `SwingUtilities.invokeLater` or `.invokeAndWait` to execute the painting on the UI thread - although you really don't want to paint outside of paintComponent(). There are a zillion options for handling this correctly, too many to go into in a comment; e.g. moving update logic to the GUI thread (easy but not ideal), posting repaint requests after each update frame and having the GUI thread query info from the Balls, storing thread-safe "snapshots" of only the data necessary to repaint, etc. – Jason C Aug 08 '13 at 17:00
  • @Jason C: While the drawing code is located in the model classes, it's executed from the paintComponent method of the drawing JPanel. Thus, it's (hopefully) on the EDT already. – Gilbert Le Blanc Aug 09 '13 at 13:50
  • Hopefully. Your pseudo code `while (running)` loop with `drawRack()` doesn't necessarily allow for that, presuming that `drawRack()` paints directly, as if the loop is on the EDT then, of course, the EDT is blocked by the loop (that's why I assumed the loop would be on another thread). That's not criticism, just an observation. :) A `javax.swing.Timer` can be used to schedule repetitive tasks on the EDT, a regular `java.util.Timer` can be used as well (not on EDT). – Jason C Aug 09 '13 at 16:46