001    package breakout;
002    
003    import java.awt.*;
004    import java.awt.geom.*;
005    import java.awt.event.*;
006    import java.util.*;
007    import javax.swing.JComponent;
008    
009    import cs101.awt.geom.*;
010    
011    /**
012     * WorldState implements the World interface, providing an
013     * augmented container for BreakoutComponents. It also serves
014     * as a synchronization point for the program.  In order for it
015     * to behave properly, it must be passed a JComponent GUI object
016     * before any components are added.  It currently uses
017     * a dumb component intersection and rebound implementation.
018     *
019     * @see breakout.World
020     *
021     * @author benmv@olin.edu
022     * @version <tt>$id$</tt>
023     */
024    public class WorldState implements World {
025        JComponent gui;    
026        Vector elements;
027        Vector listeners;
028        boolean playing;
029        int ballsLeft;
030    
031        public final Wall NORTH,SOUTH,EAST,WEST;
032    
033        /**
034         * Builds a WorldState object with only the components that
035         * make up the walls.  The South wall is a KillerWall.  If
036         * the networked version is requested, the North wall is a
037         * TransferWall.
038         *
039         * @param networked specifies whether the worldstate will
040         * be networked to another world.
041         * @see breakout.Wall
042         * @see breakout.KillerWall
043         * @see breakout.TransferWall
044         */
045        public WorldState(boolean networked) {
046            this.playing = false;
047            this.ballsLeft = 3;
048            
049            this.elements = new Vector();
050            this.listeners = new Vector();
051            if (networked)
052                this.NORTH = new TransferWall(new Point(0,-5),
053                                              new Dimension(World.MAXX-1,5),
054                                              this);
055            else
056                this.NORTH = new Wall(new Point(0,-5),
057                                      new Dimension(World.MAXX-1,5),
058                                      this);
059            this.SOUTH = new KillerWall(new Point(0,World.MAXY-1),
060                                        new Dimension(World.MAXX,5),
061                                        this);
062            this.EAST  = new Wall(new Point(World.MAXX-1,0),
063                                  new Dimension(5,World.MAXY),
064                                  this);
065            this.WEST  = new Wall(new Point(-5,0),
066                                  new Dimension(5,World.MAXY),
067                                  this);
068            add(this.NORTH);
069            add(this.SOUTH);
070            add(this.EAST);
071            add(this.WEST);
072        }    
073    
074        /**
075         * Assumes the component checking for intersections is
076         * rectangular.
077         *
078         * @see breakout.World
079         */
080        public BreakoutComponent intersects(BreakoutComponent bc) {
081    //        Shape myShape= bc.getShape();
082                    Rectangle2D myShape = bc.getShape().getBounds2D();
083            synchronized (elements) {
084                for (int i=0; i < elements.size(); i++) {
085                    BreakoutComponent other = (BreakoutComponent)elements.elementAt(i);
086                    if (other != bc && !other.isDead() &&
087                            other.getShape().intersects(myShape))
088    //                    ShapeUtils.isOverlapping(other.getShape(),myShape))
089                        return other;
090                }
091            }
092    
093            return null;
094        }
095    
096        /**
097         * Assumes that two active components rebounding are equal
098         * mass and non active components have infinite mass.
099         */
100        public void rebound(BreakoutComponent bc1, BreakoutComponent bc2) {
101            // try to get an ABC into bc1
102            if (bc2 instanceof ActiveBreakoutComponent) {
103                BreakoutComponent temp = bc1;
104                bc1 = bc2;
105                bc2 = temp;
106            }
107            // both ABCs?
108            if (bc1 instanceof ActiveBreakoutComponent &&
109                bc2 instanceof ActiveBreakoutComponent) {
110                                
111                ActiveBreakoutComponent abc1 = (ActiveBreakoutComponent)bc1;
112                ActiveBreakoutComponent abc2 = (ActiveBreakoutComponent)bc2;
113                            
114                Point c1 = abc1.getCenter();
115                            Point c2 = abc2.getCenter();
116                            
117                double xdist = Math.abs(c1.x-c2.x);
118                            double ydist = Math.abs(c1.y-c2.y);
119                            
120                Point dir1 = abc1.getDirection();
121                            Point dir2 = abc2.getDirection();
122                            
123                int temp;
124                if (xdist < ydist) {
125                    temp = dir2.y;
126                    dir2.y = dir1.y;
127                    dir1.y = temp;
128                } else {
129                    temp = dir2.x;
130                    dir2.x = dir1.x;
131                    dir1.x = temp;
132                }
133                abc1.setDirection(dir1);
134                abc2.setDirection(dir2);
135            } else if (bc1 instanceof ActiveBreakoutComponent) {
136                        
137                ActiveBreakoutComponent abc = (ActiveBreakoutComponent)bc1;
138    /*
139                Shape movingShape = bc1.getShape();
140                Shape stationaryShape = bc2.getShape();
141                            
142                Point dir = abc.getDirection();
143                            
144                System.out.println("x:"+dir.x);
145                System.out.println("y:"+dir.y);
146                double newXVel = ShapeUtils.doXReflect(movingShape,stationaryShape,dir.x,dir.y);
147                double newYVel = ShapeUtils.doYReflect(movingShape,stationaryShape,dir.x,dir.y);
148       
149                System.out.println(newXVel);
150                System.out.println(newYVel);
151       
152                Point newDir = new Point();
153                newDir.setLocation(newXVel,newYVel);
154       
155                System.out.println("new x:"+newDir.x);
156                System.out.println("new y:"+newDir.y);
157                abc.setDirection(newDir);
158    */
159                Point c1 = abc.getCenter();
160                            Point c2 = bc2.getCenter();
161                            
162                double xdist=Math.abs(c1.x-c2.x);
163                            double ydist=Math.abs(c1.y-c2.y);
164                            
165                            
166                Dimension size = bc2.getSize();
167                Point dir = abc.getDirection();
168                            
169                if (xdist/ydist < size.getWidth()/size.getHeight()) {
170                    dir.y = -dir.y;
171                } else {
172                    dir.x = -dir.x;
173                }
174                abc.setDirection(dir);
175            }  // both not ABCs.. nothing to do
176        } 
177    
178        /**
179         * adds the component to the world and sends a WorldEvent
180         * if the component added was a brick.
181         *
182         * @param bc BreakoutComponent to add
183         */
184        public void add(BreakoutComponent bc) {
185                    bc.setWorld(this);
186            elements.add(bc);
187            if (bc instanceof Brick) {
188                fireBrickAdded((Brick)bc);
189            }
190        }
191    
192        /**
193         * adds the component to the world using the add method and
194         * then registers the component with the GUI as a MouseListener
195         * and MouseMotionListener.
196         *
197         * @param bc BreakoutComponent to add to World and mouse listeners.
198         * @throws ClassCastException if the BreakoutComponent does not
199         * also implement MouseListener and MouseMotionListener.
200         */
201        public void addMouseAware(BreakoutComponent bc) {
202            add(bc);
203            this.gui.addMouseListener((MouseListener)bc);
204            this.gui.addMouseMotionListener((MouseMotionListener)bc);
205        }
206    
207        /**
208         * Returns an iterator that supports the remove operation.
209         * Remember to synchronize on this before using.
210         */
211        public Iterator iterator() {
212            final Iterator iter = elements.iterator();
213            return new Iterator() {
214                Object last;
215                public Object next() { return last=iter.next();}
216                public boolean hasNext() { return iter.hasNext(); }
217                public void remove() {
218                    iter.remove();
219                    if (last instanceof Brick) {
220                        fireBrickRemoved((Brick)last);
221                    }
222                }
223            };
224        }
225    
226        public void removeAt(Point loc) {
227            for (int i=0; i < elements.size(); i++) {
228                BreakoutComponent bc = (BreakoutComponent)elements.elementAt(i);
229                if (bc.getLocation().equals(loc)) {
230                    elements.removeElementAt(i);
231                    if (bc instanceof Brick)
232                        fireBrickRemoved((Brick)bc);
233                    return;
234                }
235            }
236        }
237        
238        public void ballLeft(Ball b) {
239            b.kill();
240            fireBallLeft(b);
241        }
242        
243        public void killAll() {
244            synchronized (this) {
245                for (int i=0; i < elements.size(); i++) {
246                    BreakoutComponent bc = (BreakoutComponent)elements.elementAt(i);
247                    bc.kill();
248                    if (bc instanceof Brick) {
249                        fireBrickRemoved((Brick)bc);
250                    }
251                }
252            }
253                    MouseListener[] mlisten = (MouseListener[])this.gui.getListeners(MouseListener.class);
254                    for (int i=0; i < mlisten.length; i++) {
255                            this.gui.removeMouseListener(mlisten[i]);
256                    }
257                    MouseMotionListener[] mmlisten = (MouseMotionListener[])this.gui.getListeners(MouseMotionListener.class);
258                    for (int i=0; i < mmlisten.length; i++) {
259                            this.gui.removeMouseMotionListener(mmlisten[i]);
260                    }               
261            try {
262                Thread.sleep(500);
263            } catch (InterruptedException e) {
264                // nothing to do
265            }
266            this.playing = false;
267            this.ballsLeft = 3;
268            this.elements = new Vector();
269            add(this.NORTH);
270            add(this.SOUTH);
271            add(this.EAST);
272            add(this.WEST);
273        }    
274    
275        public void startPlaying() {
276            this.playing = true;
277            if (this.ballsLeft == 0)
278                return;
279            else
280                this.ballsLeft--;
281            Point bloc = new Point(World.MAXX/2,World.MAXY-20);
282            Dimension bsize = new Dimension(10,10);
283            Point dir = new Point((int)((Math.random()-0.5)*200),
284                                  -(int)(Math.random()*100));
285            add(new SimpleBall(bloc,bsize,this,dir,Ball.DEFAULT_SPEED,Color.red));
286        }    
287    
288        public boolean playing() {
289            return this.playing;
290        }
291    
292        public int getBallsLeft() {
293            return this.ballsLeft;
294        }
295    
296        public void addWorldListener(WorldListener wl) {
297            this.listeners.add(wl);
298        }
299        
300        void setGui(JComponent gui) {
301            this.gui = gui;
302        }
303    
304        private void fireBrickAdded(Brick bc) {
305            if (!(bc instanceof Brick)) return;
306            WorldEvent event = WorldEvent.createEvent(WorldEvent.ADD_BRICK,bc);
307            Iterator i = this.listeners.iterator();
308            while (i.hasNext()) {
309                ((WorldListener)i.next()).componentAdded(event);
310            }
311        }
312    
313        private void fireBrickRemoved(Brick bc) {
314            WorldEvent event = WorldEvent.createEvent(WorldEvent.REMOVE_BRICK,bc);
315            Iterator it = this.listeners.iterator();
316            while (it.hasNext()) {
317                ((WorldListener)it.next()).componentRemoved(event);
318            }
319        }
320    
321        private void fireBallLeft(Ball bc) {
322            WorldEvent event = WorldEvent.createEvent(WorldEvent.BALL_TRANSFER,bc);
323            Iterator it = this.listeners.iterator();
324            while (it.hasNext()) {
325                ((WorldListener)it.next()).ballLeft(event);
326            }    
327        }
328    }
329