/* --- CSU213 Fall 2006 Lecture Notes --------- Copyright 2006 Viera K. Proulx Lecture 13: October 4, 2006 Designing Special Classes to Distinguish the Behavior */ import draw.*; import colors.*; import geometry.*; import java.util.Random; /* Now that we have the CartPt class, we can easily add to it the methods that determine whether the location is within the bounds of the given canvas --- and delete the stars that are no longer visible from our world. Additionally, we can end the world once all the stars are gone. We did not complete the design --- the move method in the class that represents the list of stars does not know what are the dimensions of the canvas --- it is 'hard-wired' to work with our example, a canvas of width 200 and height 300 - with the stars disappearing at the height 50 pixels from the bottom. We added a method that determines whether there are any stars left in the list of stars. Once all the stars have fallen down, the world stops. One of the way to find out that this has happened is by adding a method 'isEmpty' to the classes that represent the list of stars. The other way is to use the 'instanceof' operator. However, we are more concerned with the fact that there are really two kinds of stars - the live one and the burned one, yet they are both represented by the same class of data. */ /* +------------------------+ | LoStars |<------------------+ +------------------------+ | +------------------------+ | | LoStars move() | | | boolean draw(Canvas x) | | +------------------------+ | | | / \ | --- | | | ------------------------------- | | | | +------------------------+ +------------------------+ | | MTLoStars | | ConsLoStars | | +------------------------+ +------------------------+ | +------------------------+ +-| Star first | | | LoStars moveFixed() | | | LoStars rest |-+ | | LoStars move() | | +------------------------+ | | | boolean draw(Canvas x) | | | LoStars moveFixed() | | | +------------------------+ | | LoStars move() | | | | | boolean draw(Canvas x) | | | | +------------------------+ | | | +--+ v +----------------------------------+ +-------+ | Star | | Posn | +----------------------------------+ +-------+ +-| CartPt loc | | int x | | | int lifespan | | int y | | | Color color | +-------+ | +----------------------------------+ +-------+ | | Star moveFixed() | | | | Star move() | / \ | | boolean draw(Canvas c) | --- | | withinBounds(int, int, int, int) | | | +----------------------------------+ | | / \ | | --- | | | | | +------------------+ | | | BurnedStar | | | +------------------+ | | +------------------+ | | | Star moveFixed() | | | | Star move() | | | +------------------+ | v | +------------------------------------------------------------------+ | CartPt | +------------------------------------------------------------------+ +------------------------------------------------------------------+ | CartPt translate(int dx, int dy) | | CartPt translateRandom(int lowX, int highX, int lowY, int highY) | | int randomRange(int low, int high) | | boolean inBetween(int x, int low, int high) | +------------------------------------------------------------------+ */ //------------------------------------------------------------------------- //------------------------------------------------------------------------- // to represent a cartesian point class CartPt extends Posn { CartPt(int x, int y) { super(x, y); } // produce this point translated by the given dx and dy CartPt translate(int dx, int dy){ return new CartPt(this.x + dx, this.y + dy); } // produce this point translated by the random distance in each coordinate CartPt translateRandom(int lowX, int highX, int lowY, int highY){ return this.translate(this.randomRange(lowX, highX), this.randomRange(lowY, highY)); } // is this point within the given bounds boolean withinBounds(int lowX, int highX, int lowY, int highY){ return this.inBetween(this.x, lowX, highX) && this.inBetween(this.y, lowY, highY); } // produce a random distance to move left or right int randomRange(int low, int high){ return low + new Random().nextInt() % (high - low); } // is the given x between the given low and high? boolean inBetween(int x, int low, int high){ return (x >= low) && (x <= high); } } /* We now design an new subclass of the class Star that represents the burned-out stars. One of the ways we can see that we need two classes is that every method we designed for the class Star starts with an 'if' statement that distinguishes between the two variants of stars. The method moveFixed and move in the BurnedStar class are the same --- each just translates the position 4 pixels down. The only difference between the two draw methods is the color of the star. We can add a field to the Star class that specifies the color of the star. We do not need to add the color to the list of arguments for constructors, as the color is the same for all instances of each class. The lifespan of every burned-out star is zero - so we eliminate that argument from the list of arguments for the constructor as well. */ //------------------------------------------------------------------------- //------------------------------------------------------------------------- // to represent a shooting star class Star { CartPt loc; int lifespan; Color color = new White(); Star(CartPt loc, int lifespan) { this.loc = loc; this.lifespan = lifespan; } // move this star down 3 pixels if alive, 4 pixels if burned Star moveFixed(){ if (lifespan > 0) return new Star(this.loc.translate(0, 3), this.lifespan - 1); else return new BurnedStar(this.loc.translate(0, 4)); } // move this star down, flickering if alive Star move(){ if (lifespan > 0) return new Star(this.loc.translateRandom(-2, 2, 0, 3), this.lifespan - 1); else return new BurnedStar(this.loc.translate(0, 4)); } // is this star within the bounds of the given canvas? boolean withinBounds(int lowX, int highX, int lowY, int highY){ return this.loc.withinBounds(lowX, highX, lowY, highY); } // draw this star on the given canvas boolean draw(Canvas c){ return c.drawDisk(this.loc, 4, this.color); } } //------------------------------------------------------------------------- //------------------------------------------------------------------------- // to represent a burned out star class BurnedStar extends Star { BurnedStar(CartPt loc) { super(loc, 0); this.color = new Black(); } // move this star down 4 pixels Star moveFixed(){ return new BurnedStar(this.loc.translate(0, 4)); } // move this star down - burned star does not flicker Star move(){ return this.moveFixed(); } } //------------------------------------------------------------------------- //------------------------------------------------------------------------- // to represent shooting stars in our world interface LoStars { // move the stars in this list down a fixed distance LoStars moveFixed(); // move the stars in this list down LoStars move(); // is this list empty? boolean isEmpty(); // draw the stars in this list boolean draw(Canvas x); } //------------------------------------------------------------------------- //------------------------------------------------------------------------- // to represent an empty list of stars class MTLoStars implements LoStars { MTLoStars() { } // move the stars in this list down a fixed distance LoStars moveFixed() { return this; } // move the stars in this list down LoStars move() { return this; } // is this list empty? boolean isEmpty() { return true; } // draw the stars in this list boolean draw(Canvas x) { return true; } } //------------------------------------------------------------------------- //------------------------------------------------------------------------- // to represent a nonempty list of stars class ConsLoStars implements LoStars { Star first; LoStars rest; ConsLoStars(Star first, LoStars rest) { this.first = first; this.rest = rest; } /* // TEMPLATE: LoStars move() { ... this.first ... -- CartPt ... this.first.moveFixed(int, int) ... -- CartPt ... this.first.move(int, int) ... -- CartPt ... this.first.draw(Canvas) ... -- boolean ... this.rest.move(...) ... -- LoStars ... this.rest.draw(...) ... -- boolean } */ // move the stars in this list down a fixed distance LoStars moveFixed() { return new ConsLoStars(this.first.moveFixed(), this.rest.moveFixed()); } // move the stars in this list down LoStars move() { if (this.first.withinBounds(0, 200, 0, 250)) return new ConsLoStars(this.first.move(), this.rest.move()); else return this.rest.move(); } // is this list empty? boolean isEmpty() { return false; } // draw the stars in this list boolean draw(Canvas c) { return this.first.draw(c) && this.rest.draw(c); } } // the world of shooting stars class StarWorld extends World{ LoStars stars; StarWorld(LoStars stars){ this.stars = stars; } World onTick(){ if (this.stars instanceof ConsLoStars) return new StarWorld(this.stars.move()); else return this.endOfWorld("All stars have burned out."); } World onKeyEvent(String ke){ return this; } boolean draw(){ return theCanvas.drawRect(new Posn(0, 0), 200, 300, new Blue()) && this.stars.draw(this.theCanvas); } boolean erase(){ return theCanvas.drawRect(new Posn(0, 0), 200, 300, new Blue()); } } //------------------------------------------------------------------------- //------------------------------------------------------------------------- class Examples{ Examples() {} Star s1 = new Star(new CartPt(30, 0), 30); Star s2 = new Star(new CartPt(40, 10), 20); Star s3 = new Star(new CartPt(70, 20), 40); Star s4 = new Star(new CartPt(50, 60), 20); Star s5 = new Star(new CartPt(80, 30), 40); Star s6 = new Star(new CartPt(20, 20), 50); Star s7 = new Star(new CartPt(40, 40), 50); Star s8 = new Star(new CartPt(70, 30), 30); Star s9 = new Star(new CartPt(10, 40), 10); Star s10 = new Star(new CartPt(60, 40), 20); Star burned = new BurnedStar(new CartPt(60, 40)); LoStars mtstars = new MTLoStars(); LoStars stars = new ConsLoStars(this.s1, new ConsLoStars(this.s2, new ConsLoStars(this.s3, new ConsLoStars(this.s4, new ConsLoStars(this.s5, new ConsLoStars(this.s6, new ConsLoStars(this.s7, new ConsLoStars(this.s8, new ConsLoStars(this.s9, new ConsLoStars(this.s10, this.mtstars)))))))))); LoStars starsx = new ConsLoStars(this.s1, new ConsLoStars(this.burned, new ConsLoStars(this.s3, this.mtstars))); CartPt pt = new CartPt(100, 200); // test the method translate in the class CartPt boolean testTranslate = (check this.pt.translate(5, -4) expect new CartPt(105, 196)); // test the method withinBounds in the class CartPt boolean testWithinBounds = (check this.pt.withinBounds(10, 110, 20, 220) expect true) && (check this.pt.withinBounds(110, 210, 20, 220) expect false) && (check this.pt.withinBounds(10, 90, 20, 220) expect false) && (check this.pt.withinBounds(10, 110, 220, 320) expect false) && (check this.pt.withinBounds(10, 110, 20, 180) expect false); // test the method randomRange in the class CartPt boolean testRandomRange = // not a true test!! (check this.pt.randomRange(2, 5) <= 5 expect true) && (check this.pt.randomRange(2, 5) >= 2 expect true) && (check this.pt.randomRange(2, 5) <= 1 expect false) && (check this.pt.randomRange(2, 5) >= 6 expect false); // test the method inBetween in the class CartPt boolean testInBetween = (check this.pt.inBetween(4, 2, 5) expect true) && (check this.pt.inBetween(1, 2, 5) expect false) && (check this.pt.inBetween(6, 2, 5) expect false); // test the method move in the class Star and in the class BurnedStar boolean testMoveFixedStar = (check this.s1.moveFixed() expect new Star(new CartPt(30, 3), 29)) && (check this.burned.moveFixed() expect new BurnedStar(new CartPt(60, 44))); // test the method move in the class LoStars boolean testMoveFixedLoStars = (check this.mtstars.moveFixed() expect this.mtstars) && (check this.starsx.moveFixed() expect new ConsLoStars(new Star(new CartPt(30, 3), 29), new ConsLoStars(new BurnedStar(new CartPt(60, 44)), new ConsLoStars(new Star(new CartPt(70, 23), 39), this.mtstars)))); StarWorld sw = new StarWorld(this.stars); boolean go = sw.bigBang(200, 300, 0.1); }