package ballworlds.framework; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.Shape; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.ConcurrentModificationException; import javax.swing.JFrame; import javax.swing.JOptionPane; import ballworlds.ball.Ball; /** * A World simulates a "world" that contain various kinds of "balls" * (and possibly other objects). * *

* A World constructs its visual elements: *

* A World adds those visual elements to the frame that the World is given. * *

* A World manages its Balls, including animating them by calling each Ball's * act method repeatedly. * *

* A World manages its Bumpers and other objects that the World creates. * * @author David Mutchler, Salman Azhar and others, January 2005. * Modified September, 2008. */ public class World implements BallEnvironment, BumperEnvironment, CollectionOfBalls, Drawable, Runnable { private int maxX; private int maxY; private Shape shape; private Color color; private ArrayList balls; private int buttonsPanelWidth = 150; // pixels private int timeToSleep = 5; // milliseconds private static boolean hasShownErrorMessage = false; /** * Sets the World's Shape (and related data) and Color to the given values. * *

* Constructs the visual elements of this World: *

* * Adds those visual elements to the frame that it is given. * *

* Starts a Thread that repeatedly asks each Ball to act. * * @param size Size of this World (including its ButtonPanel). * @param color Color of this World. * @param ballWorldFrame JFrame to which the visual elements of this World are to be added. */ public World(Dimension size, Color color, JFrame ballWorldFrame) { Dimension worldPanelSize; Dimension buttonsPanelSize; this.maxX = size.width - this.buttonsPanelWidth; this.maxY = size.height; worldPanelSize = new Dimension(this.maxX, this.maxY); buttonsPanelSize = new Dimension(this.buttonsPanelWidth, size.height); this.shape = new Rectangle(new Point(0, 0), worldPanelSize); this.color = color; this.balls = new ArrayList(); ballWorldFrame.add(new WorldPanel(worldPanelSize, color, this)); ballWorldFrame.add(new ButtonsPanel(buttonsPanelSize, this)); new Thread(this).start(); } /** * Adds the given Ball to this World. * * @param ballToAdd the Ball to add to the World. */ public synchronized void addBall(Ball ballToAdd) { this.balls.add(ballToAdd); } /** * Removes the given Ball from this World. * * @param ballToRemove the Ball to remove from the World. */ public synchronized void removeBall(Ball ballToRemove) { this.balls.remove(ballToRemove); } /** * Returns true if the given point's x-coordinate is inside this World. * * @param p the point whose x-coordinate to check * @return true if the given point's x-coordinate is inside this World. */ public boolean isInsideWorldX(Point2D p) { return ( (p.getX() > 0) && (p.getX() < this.maxX) ); } /** * Returns true if the given point's y-coordinate is inside this World. * * @param p the point whose y-coordinate to check * @return true if the given point's y-coordinate is inside this World. */ public boolean isInsideWorldY(Point2D p) { return ( (p.getY() > 0) && (p.getY() < this.maxY)); } /** * Returns a new Point2D that is at the middle of this World. * * @return a new Point2D that is at the middle of this World. */ public Point2D middleOfWorld() { return new Point2D.Double(this.maxX / 2.0, this.maxY / 2.0); } /** * Returns the Ball in this World that is nearest the given point. * Returns null if there are no Balls in this World. * * @param p the point for which to find the nearest Ball. * @return the Ball in this World that is nearest the given point. */ public synchronized Ball nearestBall(Point2D p) { double minDistance = 0; double distance = 0; Ball closest = null; for (Ball ball : this.balls) { distance = ball.distanceFrom(p); if (closest == null || distance <= minDistance) { closest = ball; minDistance = distance; } } return closest; } /** * Draws the Balls in this World. * * @param graphics the object onto which to draw. * @param selectedBall the ball which has been selected (null if none). */ public synchronized void drawBalls(Graphics2D graphics, Ball selectedBall) { Color color; Shape shape; for (Ball ball : this.balls) { color = ball.getColor(); shape = ball.getShape(); if (color == null) { this.showNullPointerMessage("Color", ball); continue; } if (shape == null) { this.showNullPointerMessage("Shape", ball); continue; } try { if (ball == selectedBall) { graphics.setColor(WorldPanel.SELECTED_BALL_COLOR); } else { graphics.setColor(ball.getColor()); } graphics.fill(ball.getShape()); } catch (Exception exception) { try { // Should never get here. // If you do, print the stack trace and ask for help. exception.printStackTrace(); System.out.println("The framework appears to have an error. Tell your instructor."); Thread.sleep(10000); // Print error messages slowly } catch (InterruptedException e) { // If you can't sleep, no problem -- just continue. } } } } /* * Displays an error message explaining that the Ball's color/shape was null * so it could not be drawn. * Then exits the program. * Use a flag to keep multiple instances of the dialog from piling up. */ private void showNullPointerMessage(String nullAttribute, Ball ball) { if (! World.hasShownErrorMessage) { World.hasShownErrorMessage = true; String message = "READ THIS and FIX THE PROBLEM: I could not draw this Ball of type " + ball.getClass().getName() + " because its " + nullAttribute + " is null."; // XXX This dialog box does not display properly. // I don't know why. // If it were displayed properly, // the title need not be the entire message // and it is probably wrong to exit the program. JOptionPane.showMessageDialog(null, message, message, JOptionPane.ERROR_MESSAGE); System.exit(1); } } /** * Returns the Shape (which contains position and size) of this World. * * @return the Shape of this World. */ public Shape getShape() { return this.shape; } /** * Returns the Color of this World. * * @return the Color of this World. */ public Color getColor() { return this.color; } /** * Repeatedly asks each Ball to act. */ public void run() { while (true) { try { Thread.sleep(this.timeToSleep); this.act(); } catch (InterruptedException exception) { // If you can't sleep, no problem -- just continue. } } } /* * Asks each Ball to act. */ private synchronized void act() { try { for (Ball ball : this.balls) { ball.act(); } } catch (ConcurrentModificationException exception) { // If the list is modified while acting, proceed anyhow } } }