001    package components;
002    
003    import java.awt.event.ActionEvent;
004    import java.awt.event.ActionListener;
005    import java.awt.event.KeyAdapter;
006    import java.util.ArrayList;
007    import java.util.HashMap;
008    import java.util.HashSet;
009    import java.util.List;
010    import java.util.Map;
011    import java.util.Set;
012    
013    import javax.swing.Timer;
014    
015    import physics3d.Angle;
016    import physics3d.GameConstants;
017    import physics3d.PhysicsShape;
018    import physics3d.Shape;
019    import physics3d.Vect3;
020    
021    
022    /**
023     * GameObject is the most basic description of an object in gizmo ball. 
024     * It is mutable and abstract.
025     * @specfield name : String                     // the name of the object
026     * @specfield center : Vect3            // the center of rotation of the object
027     * @specfield velocity : Vect3          // the velocity of the object
028     * @specfield orientation                       // the orientation of the object
029     * @specfield gs : GameSpace            // the GameSpace that the object is in
030     * @specfield shape : Shape                 // the Shape of the object
031     * @specfield targets : set                     // the other GameObjects that this object affects
032     * @specfield frozen : boolean          // whether the GameObject reponds to forces
033     * 
034     * @derivfield tlf : Vect3                              // the top left front corner of the object
035     */
036    
037    /*
038     * Abstraction Function
039     * AF(r) = a GameObject, go, such that
040     * go.name = r.name
041     * go.tlf = r.tlf
042     * go.center = r.center
043     * go.velocity = r.velocity
044     * go.orientation is given by r.orientVect and r.orientAngle
045     * go.gs = r.gameSpace
046     * go.shape = r.shape
047     * go.targets = r.targets
048     * go.frozen = r.frozen
049     */
050    
051    /*
052     * Rep Invariant
053     * all fields cannot be null
054     * position is within the bounds of its GameSpace, as specified by the Walls of the GameSpace
055     */
056    
057    public abstract class GameObject extends KeyAdapter implements ActionListener {
058            //fields
059            protected GameSpace g;         //required - parent gamespace
060            protected Vect3 center;        //required - usually physical center, center of rotation
061            protected Vect3 orientVect;    //not required - axis of rotation
062            protected Angle orientAngle;   //not required - angle of rotation
063            protected Vect3 velocity;      //not required - velocity
064        protected Shape shape;         //same for all objects of a class
065            protected String name;         //required - unique identifier
066            protected double coRef;        //coefficent of reflection
067            protected boolean visible;     //whether or not this object should be rendered
068            protected boolean frozen;      //whether this object responds to forces
069            protected int width;           //not required, width in grid spaces (x)
070            protected int height;          //not required, height in grid spaces (y)
071            protected int depth;           //not required, depth in grid spaces (z)
072            protected int delay;           //not required, delay in ms between when action() is called and when it takes place
073            
074            //triggers
075            protected Set<GameObject> targets; //targets on collisions
076    
077            //for selected texture
078            protected boolean selected;
079            
080            //constructors
081            /**
082             * @effects creates a GameObject with zero velocity
083             */
084            public GameObject(Vect3 tlf, Vect3 ov, Angle oa, String name, GameSpace g) {
085                    this(tlf, Vect3.ZERO, ov, oa, name, g);
086            }
087            
088            /**
089             * @effects creates a GameObject with zero velocity
090             */
091            public GameObject(Map<String,String> props, GameSpace gs) {
092                    if (props == null) 
093                            throw new IllegalArgumentException("properties map to GameObject() cannot be null");
094                    if (gs == null)
095                            throw new IllegalArgumentException("gamespace to GameObject() cannot be null");
096                    //set properties to defaults, then override with given arguments
097                    Map<String, String> properties = defaults();
098                    properties.putAll(props);
099                    for (String key : properties.keySet())
100                            setProperty(key, properties.get(key));
101                    // if a properties is not in defaults or props, then checkRep will catch it
102                    center = center.plus(getDiff()); //diff points from tlf to center
103                    shape = shape();              //shape is shape w.r.t. center
104                    
105                    //set trigger targets
106                    targets = new HashSet<GameObject>();
107                    
108                    //set gamespace
109                    this.g = gs;
110                    //System.out.println("param.gs="+gs);
111                    //System.out.println("this.g="+g);
112                    
113                    //selected defaults to false
114                    selected = false;
115                    
116                    checkRep(); //check that all required properties are set
117            }
118            
119            /** check whether satisfies representation invariant
120             * @throws RuntimeException if invar not met
121             */
122            private void checkRep() {
123                    if (center == null)
124                            throw new RuntimeException("center cannot be null");
125                    if (g == null)
126                            throw new RuntimeException("g cannot be null");
127                    if (orientVect == null)
128                            throw new RuntimeException("orientVect cannot be null");
129                    if (orientAngle == null)
130                            throw new RuntimeException("orientAngle cannot be null");
131                    if (velocity == null)
132                            throw new RuntimeException("velocity cannot be null");
133                    if (shape == null)
134                            throw new RuntimeException("shape cannot be null");
135                    if (name == null)
136                            throw new RuntimeException("name cannot be null");
137            }
138            
139            /** sets all the defaults for this class. override in subclass if you want
140             * different defaults
141             *  must be called at the beginning of the constructor.
142             */
143            protected Map<String, String> defaults() {
144                    Map<String, String> defaults = new HashMap<String, String>();
145                    //defaults.put("x", "0");
146                    //defaults.put("y", "0");
147                    //defaults.put("z", "0");
148                    defaults.put("orientation", "0"); //override in flipper, for example -- orientation is required
149                    //defaults.put("name", "name");
150                    defaults.put("width", "1");
151                    defaults.put("height", "1");
152                    defaults.put("depth", "1");
153                    defaults.put("xvelocity", "0");
154                    defaults.put("yvelocity", "0");
155                    defaults.put("zvelocity", "0");
156                    defaults.put("frozen", "true");
157                    defaults.put("visible", "true");
158                    defaults.put("delay", "0");
159                    defaults.put("coref", "1.0");
160                    return defaults;
161            }
162            
163            /**
164             * @requires string s is a property in this
165             * @effects adds this property to this
166             */
167            protected void setProperty(String s1, String s2) {
168                    //Sets Map(s1) to s2
169                    s1 = s1.toLowerCase();
170                    if (s1.equals("x")) {
171                            if (center == null)
172                                    center = Vect3.ZERO;
173                            center = center.plus(Vect3.X_HAT.times(Double.valueOf(s2)));
174                    } else if (s1.equals("y")) {
175                            if (center == null)
176                                    center = Vect3.ZERO;
177                            center = center.plus(Vect3.Y_HAT.times(Double.valueOf(s2)));
178                    } else if (s1.equals("z")) {
179                            if (center == null)
180                                    center = Vect3.ZERO;
181                            center = center.plus(Vect3.Z_HAT.times(Double.valueOf(s2)));
182                    } else if (s1.equals("orientation")) {
183                            orientVect = Vect3.Z_HAT;
184                            orientAngle = new Angle(Math.PI * Double.valueOf(s2) / 180.0);
185                    } else if (s1.equals("name")) {
186                            name = s2;
187                    } else if (s1.equals("width")) {
188                            width = Integer.valueOf(s2);
189                    } else if (s1.equals("height")) {
190                            height = Integer.valueOf(s2);
191                    } else if (s1.equals("depth")) {
192                            depth = Integer.valueOf(s2);
193                    } else if (s1.equals("xvelocity")) {
194                            if (velocity == null)
195                                    velocity = Vect3.ZERO;
196                            velocity = velocity.plus(Vect3.X_HAT.times(Double.valueOf(s2)));
197                    } else if (s1.equals("yvelocity")) {
198                            if (velocity == null)
199                                    velocity = Vect3.ZERO;
200                            velocity = velocity.plus(Vect3.Y_HAT.times(Double.valueOf(s2)));
201                    } else if (s1.equals("zvelocity")) {
202                            if (velocity == null)
203                                    velocity = Vect3.ZERO;
204                            velocity = velocity.plus(Vect3.Z_HAT.times(Double.valueOf(s2)));
205                    } else if (s1.equals("coref")) {
206                            coRef = Double.valueOf(s2);
207                    } else if (s1.equals("frozen")) {
208                            frozen = Boolean.valueOf(s2);
209                    } else if (s1.equals("visible")) {
210                            visible = Boolean.valueOf(s2);
211                    } else if (s1.equals("delay")) {
212                            delay = Integer.valueOf(s2);
213                    } else {
214                            //This case is ok, it just means an invalid property was given, failing
215                            //is not the appropriate response
216                            //System.out.println("Invalid property given ...key=" + s1 + " value="+s2);
217    //                      throw new RuntimeException("wtf, mate? s1=" + s1 + ", s2="+s2);
218                    }
219            }
220            
221            /**
222             * @effects creates a GameObject
223             */
224            public GameObject(Vect3 tlf, Vect3 velocity, Vect3 ov, Angle oa, String name, GameSpace g) {
225                    this.velocity = velocity;
226                    this.orientVect = ov;
227                    this.orientAngle = oa;
228                    this.name = name;
229                    this.g = g;
230                    this.targets = new HashSet<GameObject>();
231                    this.visible = true;
232                    this.frozen = false;
233                    
234                    //centerRotation will be set in subclasses
235            }
236            
237            //methods
238        /** Difference between TLF and center. ZERO for balls, so should be overridden
239         * @return a Vect3 that points from TLF to center. by default this is the center by width, height, depth
240         */ 
241            public Vect3 getDiff() {
242                    return new Vect3(width/2.0, height/2.0, depth/2.0);
243            }
244            
245    
246            
247            //overridden by each subclass
248            /** the Shape of the object **/
249            protected abstract Shape shape();
250            
251            /** gets the top-left-front corner, rounded for double precision things
252             * @return the position of this object, rounded
253             */
254            public Vect3 getTLF() {
255                    //tlf + diff = center
256                    ////System.out.println(this + " " + center + getDiff());
257                    Vect3 tfl = center.minus(getDiff());
258                    Vect3 roundedTLF = new Vect3((int)Math.round(tfl.x()),(int)Math.round(tfl.y()),(int)Math.round(tfl.z()));
259                    return roundedTLF;
260            }
261            
262            /** gets the top-left-front corner, no rounding
263             * @return the TLF in its original form
264             */
265            public Vect3 getNonRoundedTLF() {
266                    return center.minus(getDiff());
267            }
268            
269            /** convenience method -- getTLF() is rounded
270             * @return the position of this object, rounded
271             */
272            public Vect3 getRoundedTLF() {
273                    return getTLF();
274            }
275            
276            /** Set the top-left-front corner of the object
277             * @requires tlf != null
278             * @effects sets the top left corner of the object
279             */
280            public void setTLF(Vect3 tlf) {
281                    center = tlf.plus(getDiff());
282            }
283            
284            /** 
285             * @return this object's velocity
286             */
287            public Vect3 getVelocity() {
288                    return velocity;
289            }
290            
291            /**
292             * @requires newVel != null
293             * @modifies velocity
294             * @effects sets velocity to newVel
295             */
296            public void setVelocity(Vect3 newVel) {
297                    velocity = newVel;
298            }
299            
300            /**
301             * @return the gamespace this object is in
302             */
303            public GameSpace getGameSpace() {
304                    return g;
305            }
306            
307            /**
308             * @return this.name
309             */
310            
311            public String getName() {
312                    return this.name;
313            }
314            
315            /** get the center of rotation of the object
316             * @return the center of roation of the object
317             */
318            public Vect3 getCenter() {
319                    return center;
320            }
321            /**
322             * @effects changes the center of rotation for the object
323             */
324            public void setCenter(Vect3 c) {
325                    this.center = c;
326            }
327            
328            /**
329             * @return the orientation angle of the object
330             */
331            public Angle getOrientAngle() {
332                    return orientAngle;
333            }
334            
335            /**
336             * @requires newAngle != null
337             * @modifies orientAngle
338             * @effects sets orientAngle to newAngle
339             */
340            public void setOrientAngle(Angle newAngle) {
341                    orientAngle = newAngle;
342            }
343    
344            /**
345             * @return the orientation vector of the object
346             */
347            public Vect3 getOrientVector() {
348                    return orientVect;
349            }
350            
351            /**
352             * @requires newVect != null
353             * @modifies orientVect
354             * @effects sets orientVect to newVect
355             */
356            public void setOrientVect(Vect3 newVect) {
357                    orientVect = newVect;
358            }
359            
360            
361            /**
362             * @return the targets of this object
363             */
364            public Set<GameObject> getTargets() {
365                    return targets;
366            }
367            
368            /**
369             * @requires obj != null, obj.gs == this.gs
370             * @modifies targets
371             * @effects adds obj to targets
372             */
373            public void addTarget(GameObject obj) {
374                    targets.add(obj);
375            }
376            
377            /**
378             * @modifies targets
379             * @effects removes all targets of this
380             */
381            public void clearTargets() {
382                    targets.clear();
383            }
384            
385            /**
386             * @modifies targets
387             * @effects removes the given target, if present
388             */
389            public boolean removeTarget(GameObject t) {
390                    return targets.remove(t);
391            }
392            
393            /**
394             * This is overriden in mobile objects
395             * @effects specifies what the GameObject does in a frame
396             */
397            public void stepFrame() { }
398            
399            /** Schedules actionPerformed() to happen after this.delay **/
400            public void action() {
401                    Timer t = new Timer(delay, this);
402                    t.setRepeats(false);
403                    t.start();
404            }
405            /**
406             * This is overriden in objects where something actually occurs when triggered,
407             * for example, Flippers
408             * @effects specifies what the GameObject does when it is triggered.
409             */
410            public void actionPerformed(ActionEvent e) {};
411            
412            /**
413             * @effects iterates through targets and tell them to perform action()
414             */
415            public void onCollision(GameObject projectile) {
416                    //most objects don't care about what hit them
417                    for (GameObject g : targets) {
418                            g.action(); //call triggers
419                    }
420            }
421            
422            /**
423             * @return a valid hash code for this object
424             */
425            public int hashCode() {
426                    return 1;
427            }
428            
429            /**
430             * @return go != null && (go instanceof GameObject) && ...
431             */
432            public boolean equals(Object go) {
433                    if ((go != null) && (go instanceof GameObject)) {
434                            GameObject myObj = (GameObject) go;
435                            return g.equals(myObj.getGameSpace()) && 
436                                    center.equals(myObj.getCenter()) && 
437                                    orientVect.equals(myObj.getOrientVector()) && 
438                                    orientAngle.equals(myObj.getOrientAngle()) && 
439                                    velocity.equals(myObj.getVelocity()) && 
440                                    name.equals(myObj.getName());
441                    }
442                    return false;
443            }
444            
445            
446            /**
447             * @return the Shape describing this Object
448             */
449            public Shape getShape() {
450                    return shape;
451            }
452    
453            /**
454             * @return the bounding shape of this
455             */
456            public PhysicsShape getBounds() {
457                    return this.getShape().getBound();
458            }
459    
460            /**
461             * @return true if the object is visible
462             */
463            public boolean isVisible() {
464                    return visible;
465            }
466    
467            /**
468             * @param visible the visible to set
469             */
470            public void setVisible(boolean visible) {
471                    this.visible = visible;
472            }
473    
474            /**
475             * @return true if frozen
476             */
477            public boolean isFrozen() {
478                    return frozen;
479            }
480    
481            /**
482             * @param frozen the frozen to set
483             */
484            public void setFrozen(boolean frozen) {
485                    this.frozen = frozen;
486            }
487            
488            /**
489             * changes the map to represent the values of this GameObject required by the xml spec
490             * @modifies m
491             */
492            public void getBasicPropertyMap(Map<String,String> m) {
493    
494                    if (this.delay != 0) {
495                            m.put("delay", Integer.toString(delay));
496                    }
497                    if (this.getGOClassification() != GameObjectClassification.BALL) {
498                            putInVect3(getRoundedTLF(), m, "",true);
499                    }
500                    else {
501                            if (this.frozen) {
502                                    m.put("frozen", Boolean.toString(this.frozen));
503                            }
504                            putInVect3(getCenter(), m, "",true);
505                    }
506                    m.put("name",name);
507            }
508            
509            /**
510             * Given a property map, a vector, and a string, adds the keys 
511             * x + title, y + title, and z + title with the corresponding values from v, to m
512             * @requires no variables passed are null
513             * @modifies m, if round is true, puts them in as ints, otherwise as doubles
514             */
515            public static void putInVect3(Vect3 v, Map<String,String> m, String title, boolean round) {
516                    if (round) {
517                            m.put("x" + title, Integer.toString((int)v.x()));
518                            m.put("y" + title, Integer.toString((int)v.y()));
519                            m.put("z" + title, Integer.toString((int)v.z()));
520                    }
521                    else {
522                            m.put("x" + title, Double.toString(v.x()));
523                            m.put("y" + title, Double.toString(v.y()));
524                            m.put("z" + title, Double.toString(v.z()));
525                    }
526            }
527            
528            /**
529             * Convience overload of putInVect3, this prevents rounding
530             */
531            public static void putInVect3(Vect3 v, Map<String,String> m, String title) {
532                    putInVect3(v, m, title,false);
533            }
534            
535            /**
536             * Requires that all obejects return their own classification
537             */
538            public abstract GameObjectClassification getGOClassification();
539            
540            /**
541             * @return the top left front coner points of all the grid positions occupied 
542             * by this GameObject
543             */
544            public abstract Set<Vect3> getOccupiedPositions();
545            
546            /**
547             * @return whether this GameObject is selected
548             */
549            public boolean isSelected() {
550                    return selected;
551            }       
552            
553            /**
554             * @effects sets selected to s
555             */
556            public void setSelected(boolean s) {
557                    selected = s;
558            }
559    
560            /**
561             * @return the depth
562             */
563            public int getDepth() {
564                    return depth;
565            }
566    
567            /**
568             * @param depth the depth to set
569             */
570            public void setDepth(int depth) {
571                    this.depth = depth;
572            }
573    
574            /**
575             * @return the height
576             */
577            public int getHeight() {
578                    return height;
579            }
580    
581            /**
582             * @requires height > 0
583             * @modifies this.height
584             * @effects sets this.height to height
585             */
586            public void setHeight(int height) {
587                    this.height = height;
588            }
589    
590            /**
591             * @return the width
592             */
593            public int getWidth() {
594                    return width;
595            }
596    
597            /**
598             * @requires width > 0
599             * @modifies this.width
600             * @effects sets this.width to width
601             */
602            public void setWidth(int width) {
603                    this.width = width;
604            }
605            
606            /**
607             * @requires d >= 0
608             * @modifies delay
609             * @effects sets delay to d
610             */
611            public void setDelay(int d) {
612                    delay = d;
613            }
614            
615            /**
616             * @return the delay time of this
617             */
618            public int getDelay() {
619                    return delay;
620            }
621            
622            /** string representation of this object. ROUNDED coordinates. **/
623            public String toString() {
624                    return name+"<"+Vect3.roundToNearest100(center.x())+","
625                    +Vect3.roundToNearest100(center.y())+","
626                    +Vect3.roundToNearest100(center.z())+">";
627            }
628    
629            /**
630             * @return the coRef
631             */
632            public double getCoRef() {
633                    return coRef;
634            }
635    
636            /**
637             * @param coRef the coRef to set
638             */
639            public void setCoRef(double coRef) {
640                    this.coRef = coRef;
641            }
642    }